Merge "Throw for showSystemOutputSwitcher() call from privileged router" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index df8f581..f5db575 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -26453,7 +26453,7 @@
     method @NonNull public final java.util.List<android.media.midi.MidiReceiver> getOutputPortReceivers();
     method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
     method public void onClose();
-    method public void onDeviceStatusChanged(@Nullable android.media.midi.MidiDeviceStatus);
+    method public void onDeviceStatusChanged(@NonNull android.media.midi.MidiDeviceStatus);
     method @NonNull public abstract java.util.List<android.media.midi.MidiReceiver> onGetInputPortReceivers();
     field public static final String SERVICE_INTERFACE = "android.media.midi.MidiUmpDeviceService";
   }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 77168e3..0185080 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -647,6 +647,7 @@
     field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio";
     field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast";
     field public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = "android:receive_explicit_user_interaction_audio";
+    field public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO = "android:receive_sandbox_trigger_audio";
     field public static final String OPSTR_REQUEST_DELETE_PACKAGES = "android:request_delete_packages";
     field public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages";
     field public static final String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background";
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index bcd43ea..096ad55 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -2256,6 +2256,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final String OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO =
             "android:receive_sandbox_trigger_audio";
 
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index d189bab..2c428ef 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -236,6 +236,7 @@
      *              {@link android.view.WindowManagerPolicyConstants#KEYGUARD_GOING_AWAY_FLAG_TO_SHADE}
      *              etc.
      */
+     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_KEYGUARD)")
     void keyguardGoingAway(int flags);
 
     void suppressResizeConfigChanges(boolean suppress);
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 723c564..5d7993d 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -2929,19 +2929,16 @@
         ComponentName cn = null;
         String[] cmfWallpaperMap = context.getResources().getStringArray(
                 com.android.internal.R.array.default_wallpaper_component_per_device_color);
-        if (cmfWallpaperMap == null || cmfWallpaperMap.length == 0) {
-            Log.d(TAG, "No CMF wallpaper config");
-            return getDefaultWallpaperComponent(context);
-        }
-
-        for (String entry : cmfWallpaperMap) {
-            String[] cmfWallpaper;
-            if (!TextUtils.isEmpty(entry)) {
-                cmfWallpaper = entry.split(",");
-                if (cmfWallpaper != null && cmfWallpaper.length == 2 && VALUE_CMF_COLOR.equals(
-                        cmfWallpaper[0]) && !TextUtils.isEmpty(cmfWallpaper[1])) {
-                    cn = ComponentName.unflattenFromString(cmfWallpaper[1]);
-                    break;
+        if (cmfWallpaperMap != null && cmfWallpaperMap.length > 0) {
+            for (String entry : cmfWallpaperMap) {
+                String[] cmfWallpaper;
+                if (!TextUtils.isEmpty(entry)) {
+                    cmfWallpaper = entry.split(",");
+                    if (cmfWallpaper != null && cmfWallpaper.length == 2 && VALUE_CMF_COLOR.equals(
+                            cmfWallpaper[0]) && !TextUtils.isEmpty(cmfWallpaper[1])) {
+                        cn = ComponentName.unflattenFromString(cmfWallpaper[1]);
+                        break;
+                    }
                 }
             }
         }
@@ -2950,7 +2947,7 @@
             cn = null;
         }
 
-        return cn;
+        return cn == null ? getDefaultWallpaperComponent(context) : cn;
     }
 
     private static boolean isComponentExist(Context context, ComponentName cn) {
diff --git a/core/java/android/content/res/Element.java b/core/java/android/content/res/Element.java
index 518744e..a86c0c9 100644
--- a/core/java/android/content/res/Element.java
+++ b/core/java/android/content/res/Element.java
@@ -746,7 +746,6 @@
             case TAG_PROVIDER:
             case TAG_RECEIVER:
             case TAG_SERVICE:
-            case TAG_USES_LIBRARY:
                 switch (name) {
                     case TAG_ATTR_NAME:
                         return true;
@@ -776,8 +775,6 @@
                 return index ==  R.styleable.AndroidManifestReceiver_name;
             case TAG_SERVICE:
                 return index ==  R.styleable.AndroidManifestService_name;
-            case TAG_USES_LIBRARY:
-                return index ==  R.styleable.AndroidManifestUsesLibrary_name;
             default:
                 return false;
         }
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 3a66081..c872516 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -3535,7 +3535,7 @@
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode.
      * They can be queried through
      * {@link android.hardware.camera2.CameraCharacteristics#get } with
-     * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION) }.
+     * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION }.
      * Unless reported by both
      * {@link android.hardware.camera2.params.StreamConfigurationMap }s, the outputs from
      * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code> and
@@ -3550,13 +3550,12 @@
      * <ul>
      * <li>
      * <p>The mandatory stream combinations listed in
-     *   {@link android.hardware.camera2.CameraCharacteristics.mandatoryMaximumResolutionStreamCombinations }
-     *   would not apply.</p>
+     *   {@link CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS android.scaler.mandatoryMaximumResolutionStreamCombinations}  would not apply.</p>
      * </li>
      * <li>
      * <p>The bayer pattern of {@code RAW} streams when
      *   {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
-     *   is selected will be the one listed in {@link android.sensor.info.binningFactor }.</p>
+     *   is selected will be the one listed in {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.</p>
      * </li>
      * <li>
      * <p>The following keys will always be present:</p>
@@ -3576,9 +3575,11 @@
      *
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      *
+     * @see CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+     * @see CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR
      * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION
      * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
      * @see #SENSOR_PIXEL_MODE_DEFAULT
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 1536376..57f7bca 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -4460,7 +4460,7 @@
      * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_DEFAULT } mode.
      * They can be queried through
      * {@link android.hardware.camera2.CameraCharacteristics#get } with
-     * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION) }.
+     * {@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION }.
      * Unless reported by both
      * {@link android.hardware.camera2.params.StreamConfigurationMap }s, the outputs from
      * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code> and
@@ -4475,13 +4475,12 @@
      * <ul>
      * <li>
      * <p>The mandatory stream combinations listed in
-     *   {@link android.hardware.camera2.CameraCharacteristics.mandatoryMaximumResolutionStreamCombinations }
-     *   would not apply.</p>
+     *   {@link CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS android.scaler.mandatoryMaximumResolutionStreamCombinations}  would not apply.</p>
      * </li>
      * <li>
      * <p>The bayer pattern of {@code RAW} streams when
      *   {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }
-     *   is selected will be the one listed in {@link android.sensor.info.binningFactor }.</p>
+     *   is selected will be the one listed in {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.</p>
      * </li>
      * <li>
      * <p>The following keys will always be present:</p>
@@ -4501,9 +4500,11 @@
      *
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      *
+     * @see CameraCharacteristics#SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP
      * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
+     * @see CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR
      * @see CameraCharacteristics#SENSOR_INFO_PIXEL_ARRAY_SIZE_MAXIMUM_RESOLUTION
      * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
      * @see #SENSOR_PIXEL_MODE_DEFAULT
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 299e7f1..e6bdfe1 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -115,7 +115,6 @@
     private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default";
     private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle";
     private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native";
-    private static final String SYSTEM_ANGLE_STRING = "system";
 
     private static final String PROPERTY_RO_ANGLE_SUPPORTED = "ro.gfx.angle.supported";
 
@@ -196,16 +195,15 @@
     }
 
     /**
-     * Query to determine the ANGLE driver choice.
+     * Query to determine if ANGLE should be used
      */
-    private String queryAngleChoice(Context context, Bundle coreSettings,
-                                               String packageName) {
+    private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) {
         if (TextUtils.isEmpty(packageName)) {
             Log.v(TAG, "No package name specified; use the system driver");
-            return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
+            return false;
         }
 
-        return queryAngleChoiceInternal(context, coreSettings, packageName);
+        return shouldUseAngleInternal(context, coreSettings, packageName);
     }
 
     private int getVulkanVersion(PackageManager pm) {
@@ -426,11 +424,10 @@
      *    forces a choice;
      * 3) Use ANGLE if isAngleEnabledByGameMode() returns true;
      */
-    private String queryAngleChoiceInternal(Context context, Bundle bundle,
-                                                       String packageName) {
+    private boolean shouldUseAngleInternal(Context context, Bundle bundle, String packageName) {
         // Make sure we have a good package name
         if (TextUtils.isEmpty(packageName)) {
-            return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
+            return false;
         }
 
         // Check the semi-global switch (i.e. once system has booted enough) for whether ANGLE
@@ -445,7 +442,7 @@
         }
         if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) {
             Log.v(TAG, "Turn on ANGLE for all applications.");
-            return ANGLE_GL_DRIVER_CHOICE_ANGLE;
+            return true;
         }
 
         // Get the per-application settings lists
@@ -468,8 +465,7 @@
                             + optInPackages.size() + ", "
                         + "number of values: "
                             + optInValues.size());
-            return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE
-                    : ANGLE_GL_DRIVER_CHOICE_DEFAULT;
+            return mEnabledByGameMode;
         }
 
         // See if this application is listed in the per-application settings list
@@ -477,8 +473,7 @@
 
         if (pkgIndex < 0) {
             Log.v(TAG, packageName + " is not listed in per-application setting");
-            return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE
-                    : ANGLE_GL_DRIVER_CHOICE_DEFAULT;
+            return mEnabledByGameMode;
         }
         mAngleOptInIndex = pkgIndex;
 
@@ -489,14 +484,13 @@
                 "ANGLE Developer option for '" + packageName + "' "
                         + "set to: '" + optInValue + "'");
         if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) {
-            return ANGLE_GL_DRIVER_CHOICE_ANGLE;
+            return true;
         } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
-            return ANGLE_GL_DRIVER_CHOICE_NATIVE;
+            return false;
         } else {
             // The user either chose default or an invalid value; go with the default driver or what
             // the game mode indicates
-            return mEnabledByGameMode ? ANGLE_GL_DRIVER_CHOICE_ANGLE
-                    : ANGLE_GL_DRIVER_CHOICE_DEFAULT;
+            return mEnabledByGameMode;
         }
     }
 
@@ -563,12 +557,8 @@
      */
     private boolean setupAngle(Context context, Bundle bundle, PackageManager packageManager,
             String packageName) {
-        final String angleChoice = queryAngleChoice(context, bundle, packageName);
-        if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_DEFAULT)) {
-            return false;
-        }
-        if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
-            nativeSetAngleInfo("", true, packageName, null);
+
+        if (!shouldUseAngle(context, bundle, packageName)) {
             return false;
         }
 
@@ -637,10 +627,10 @@
             Log.d(TAG, "ANGLE package libs: " + paths);
         }
 
-        // If we make it to here, ANGLE apk will be used.  Call nativeSetAngleInfo() with the
-        // application package name and ANGLE features to use.
+        // If we make it to here, ANGLE will be used.  Call setAngleInfo() with the package name,
+        // and features to use.
         final String[] features = getAngleEglFeatures(context, bundle);
-        nativeSetAngleInfo(paths, false, packageName, features);
+        setAngleInfo(paths, false, packageName, features);
 
         return true;
     }
@@ -662,10 +652,10 @@
             return false;
         }
 
-        // If we make it to here, system ANGLE will be used.  Call nativeSetAngleInfo() with
-        // the application package name and ANGLE features to use.
+        // If we make it to here, ANGLE will be used.  Call setAngleInfo() with the package name,
+        // and features to use.
         final String[] features = getAngleEglFeatures(context, bundle);
-        nativeSetAngleInfo(SYSTEM_ANGLE_STRING, false, packageName, features);
+        setAngleInfo("", true, packageName, features);
         return true;
     }
 
@@ -946,8 +936,8 @@
     private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries);
     private static native void setGpuStats(String driverPackageName, String driverVersionName,
             long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion);
-    private static native void nativeSetAngleInfo(String path, boolean useNativeDriver,
-            String packageName, String[] features);
+    private static native void setAngleInfo(String path, boolean useSystemAngle, String packageName,
+            String[] features);
     private static native boolean setInjectLayersPrSetDumpable();
     private static native void nativeToggleAngleAsSystemDriver(boolean enabled);
 
diff --git a/core/java/android/util/apk/TEST_MAPPING b/core/java/android/util/apk/TEST_MAPPING
index b26a38b..a3a86fd 100644
--- a/core/java/android/util/apk/TEST_MAPPING
+++ b/core/java/android/util/apk/TEST_MAPPING
@@ -15,7 +15,7 @@
           "include-filter": "android.content.pm.cts.PackageManagerShellCommandIncrementalTest"
         },
         {
-          "include-filter": "android.content.pm.cts.PackageManagerShellCommandInstallerTest"
+          "include-filter": "android.content.pm.cts.PackageManagerShellCommandInstallTest"
         }
       ]
     }
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index 8fc30d1..afc3cbd 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -50,7 +50,7 @@
                                                     appPackageNameChars.c_str(), vulkanVersion);
 }
 
-void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useNativeDriver,
+void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useSystemAngle,
                          jstring packageName, jobjectArray featuresObj) {
     ScopedUtfChars pathChars(env, path);
     ScopedUtfChars packageNameChars(env, packageName);
@@ -73,7 +73,7 @@
         }
     }
 
-    android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useNativeDriver,
+    android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useSystemAngle,
                                                      packageNameChars.c_str(), features);
 }
 
@@ -118,7 +118,7 @@
          reinterpret_cast<void*>(setGpuStats_native)},
         {"setInjectLayersPrSetDumpable", "()Z",
          reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)},
-        {"nativeSetAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
+        {"setAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V",
          reinterpret_cast<void*>(setAngleInfo_native)},
         {"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V",
          reinterpret_cast<void*>(setLayerPaths_native)},
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 64be87b..7300772 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5565,10 +5565,6 @@
     <!-- pdp data reject retry delay in ms -->
     <integer name="config_pdp_reject_retry_delay_ms">-1</integer>
 
-    <!-- Duration in milliseconds for device to vibrate on mash press on power
-         button. -->
-    <integer name="config_mashPressVibrateTimeOnPowerButton">0</integer>
-
     <!-- Whether or not to enable the binder heavy hitter watcher by default -->
     <bool name="config_defaultBinderHeavyHitterWatcherEnabled">false</bool>
 
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index b7a5bc8..18abe70 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -199,4 +199,8 @@
     <bool name="allow_clear_initial_attach_data_profile">false</bool>
     <java-symbol type="bool" name="allow_clear_initial_attach_data_profile" />
 
+    <!-- Boolean indicating whether TelephonyAnalytics module is active or not. -->
+    <bool name="telephony_analytics_switch">true</bool>
+    <java-symbol type="bool" name="telephony_analytics_switch" />
+
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3aae3ca..391d7bd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4949,8 +4949,6 @@
   <java-symbol type="array" name="config_builtInDisplayIsRoundArray" />
   <java-symbol type="array" name="config_gnssParameters" />
 
-  <java-symbol type="integer" name="config_mashPressVibrateTimeOnPowerButton" />
-
   <java-symbol type="string" name="config_systemGameService" />
 
   <java-symbol type="string" name="config_supervisedUserCreationPackage"/>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index b0afa68..66b9ade6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -821,13 +821,17 @@
      * @param interceptBack whether back should be intercepted or not.
      */
     void updateWindowFlagsForBackpress(boolean interceptBack) {
-        if (mStackView != null && mAddedToWindowManager) {
+        if (mAddedToWindowManager) {
             mWmLayoutParams.flags = interceptBack
                     ? 0
                     : WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                             | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
             mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
-            mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
+            if (mStackView != null) {
+                mWindowManager.updateViewLayout(mStackView, mWmLayoutParams);
+            } else if (mLayerView != null) {
+                mWindowManager.updateViewLayout(mLayerView, mWmLayoutParams);
+            }
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 76ca68b..517f9f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -54,6 +54,15 @@
     public static final boolean IS_DISPLAY_CHANGE_ENABLED = SystemProperties.getBoolean(
             "persist.wm.debug.desktop_change_display", false);
 
+
+    /**
+     * Flag to indicate that desktop stashing is enabled.
+     * When enabled, swiping home from desktop stashes the open apps. Next app that launches,
+     * will be added to the desktop.
+     */
+    private static final boolean IS_STASHING_ENABLED = SystemProperties.getBoolean(
+            "persist.wm.debug.desktop_stashing", false);
+
     /**
      * Return {@code true} if desktop mode support is enabled
      */
@@ -84,6 +93,13 @@
     }
 
     /**
+     * Return {@code true} if desktop task stashing is enabled when going home.
+     * Allows users to use home screen to add tasks to desktop.
+     */
+    public static boolean isStashingEnabled() {
+        return IS_STASHING_ENABLED;
+    }
+    /**
      * Check if desktop mode is active
      *
      * @return {@code true} if active
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 9bc28a46..b15fd91 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -150,20 +150,24 @@
      * back to front during the launch.
      */
     fun stashDesktopApps(displayId: Int) {
-        KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps")
-        desktopModeTaskRepository.setStashed(displayId, true)
+        if (DesktopModeStatus.isStashingEnabled()) {
+            KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: stashDesktopApps")
+            desktopModeTaskRepository.setStashed(displayId, true)
+        }
     }
 
     /**
      * Clear the stashed state for the given display
      */
     fun hideStashedDesktopApps(displayId: Int) {
-        KtProtoLog.v(
-                WM_SHELL_DESKTOP_MODE,
-                "DesktopTasksController: hideStashedApps displayId=%d",
-                displayId
-        )
-        desktopModeTaskRepository.setStashed(displayId, false)
+        if (DesktopModeStatus.isStashingEnabled()) {
+            KtProtoLog.v(
+                    WM_SHELL_DESKTOP_MODE,
+                    "DesktopTasksController: hideStashedApps displayId=%d",
+                    displayId
+            )
+            desktopModeTaskRepository.setStashed(displayId, false)
+        }
     }
 
     /** Get number of tasks that are marked as visible */
@@ -172,9 +176,13 @@
     }
 
     /** Move a task with given `taskId` to desktop */
-    fun moveToDesktop(taskId: Int, wct: WindowContainerTransaction = WindowContainerTransaction()) {
+    fun moveToDesktop(
+            decor: DesktopModeWindowDecoration,
+            taskId: Int,
+            wct: WindowContainerTransaction = WindowContainerTransaction()
+    ) {
         shellTaskOrganizer.getRunningTaskInfo(taskId)?.let {
-            task -> moveToDesktop(task, wct)
+            task -> moveToDesktop(decor, task, wct)
         }
     }
 
@@ -182,6 +190,7 @@
      * Move a task to desktop
      */
     fun moveToDesktop(
+            decor: DesktopModeWindowDecoration,
             task: RunningTaskInfo,
             wct: WindowContainerTransaction = WindowContainerTransaction()
     ) {
@@ -195,7 +204,7 @@
         addMoveToDesktopChanges(wct, task)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
+            enterDesktopTaskTransitionHandler.moveToDesktop(wct, decor)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 22929c76..16b2393 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -20,6 +20,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
 import android.app.ActivityManager;
 import android.graphics.PointF;
@@ -36,6 +37,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration;
 import com.android.wm.shell.windowdecor.MoveToDesktopAnimator;
 
 import java.util.ArrayList;
@@ -60,6 +62,7 @@
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
     private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
     private MoveToDesktopAnimator mMoveToDesktopAnimator;
+    private DesktopModeWindowDecoration mDesktopModeWindowDecoration;
 
     public EnterDesktopTaskTransitionHandler(
             Transitions transitions) {
@@ -128,6 +131,18 @@
                 onAnimationEndCallback);
     }
 
+    /**
+     * Starts Transition of type TRANSIT_MOVE_TO_DESKTOP
+     * @param wct WindowContainerTransaction for transition
+     * @param decor {@link DesktopModeWindowDecoration} of task being animated
+     */
+    public void moveToDesktop(@NonNull WindowContainerTransaction wct,
+            DesktopModeWindowDecoration decor) {
+        mDesktopModeWindowDecoration = decor;
+        startTransition(Transitions.TRANSIT_MOVE_TO_DESKTOP, wct,
+                null /* onAnimationEndCallback */);
+    }
+
     @Override
     public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startT,
@@ -167,138 +182,209 @@
         }
 
         final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
-        if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE
+        if (type == Transitions.TRANSIT_MOVE_TO_DESKTOP
                 && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
-            // Transitioning to freeform but keeping fullscreen bounds, so the crop is set
-            // to null and we don't require an animation
-            final SurfaceControl sc = change.getLeash();
-            startT.setWindowCrop(sc, null);
-
-            if (mMoveToDesktopAnimator == null
-                    || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
-                Slog.e(TAG, "No animator available for this transition");
-                return false;
-            }
-
-            // Calculate and set position of the task
-            final PointF position = mMoveToDesktopAnimator.getPosition();
-            startT.setPosition(sc, position.x, position.y);
-            finishT.setPosition(sc, position.x, position.y);
-
-            startT.apply();
-
-            mTransitions.getMainExecutor().execute(
-                    () -> finishCallback.onTransitionFinished(null, null));
-
-            return true;
+            return animateMoveToDesktop(change, startT, finishCallback);
         }
 
-        Rect endBounds = change.getEndAbsBounds();
+        if (type == Transitions.TRANSIT_START_DRAG_TO_DESKTOP_MODE
+                && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+            return animateStartDragToDesktopMode(change, startT, finishT, finishCallback);
+        }
+
+        final Rect endBounds = change.getEndAbsBounds();
         if (type == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
                 && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
                 && !endBounds.isEmpty()) {
-            // This Transition animates a task to freeform bounds after being dragged into freeform
-            // mode and brings the remaining freeform tasks to front
-            final SurfaceControl sc = change.getLeash();
-            startT.setWindowCrop(sc, endBounds.width(),
-                    endBounds.height());
-            startT.apply();
-
-            // End the animation that shrinks the window when task is first dragged from fullscreen
-            if (mMoveToDesktopAnimator != null) {
-                mMoveToDesktopAnimator.endAnimator();
-            }
-
-            // We want to find the scale of the current bounds relative to the end bounds. The
-            // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be
-            // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to
-            // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds
-            final ValueAnimator animator =
-                    ValueAnimator.ofFloat(
-                            MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
-            animator.setDuration(FREEFORM_ANIMATION_DURATION);
-            final SurfaceControl.Transaction t = mTransactionSupplier.get();
-            animator.addUpdateListener(animation -> {
-                final float animationValue = (float) animation.getAnimatedValue();
-                t.setScale(sc, animationValue, animationValue);
-
-                final float animationWidth = endBounds.width() * animationValue;
-                final float animationHeight = endBounds.height() * animationValue;
-                final int animationX = endBounds.centerX() - (int) (animationWidth / 2);
-                final int animationY = endBounds.centerY() - (int) (animationHeight / 2);
-
-                t.setPosition(sc, animationX, animationY);
-                t.apply();
-            });
-
-            animator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    if (mOnAnimationFinishedCallback != null) {
-                        mOnAnimationFinishedCallback.accept(finishT);
-                    }
-                    mTransitions.getMainExecutor().execute(
-                            () -> finishCallback.onTransitionFinished(null, null));
-                }
-            });
-
-            animator.start();
-            return true;
+            return animateFinalizeDragToDesktopMode(change, startT, finishT, finishCallback,
+                    endBounds);
         }
 
         if (type == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
                 && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
-            // This Transition animates a task to fullscreen after being dragged from the status
-            // bar and then released back into the status bar area
-            final SurfaceControl sc = change.getLeash();
-            // Hide the first (fullscreen) frame because the animation will start from the smaller
-            // scale size.
-            startT.hide(sc)
-                    .setWindowCrop(sc, endBounds.width(), endBounds.height())
-                    .apply();
-
-            if (mMoveToDesktopAnimator == null
-                    || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
-                Slog.e(TAG, "No animator available for this transition");
-                return false;
-            }
-
-            // End the animation that shrinks the window when task is first dragged from fullscreen
-            mMoveToDesktopAnimator.endAnimator();
-
-            final ValueAnimator animator = new ValueAnimator();
-            animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f);
-            animator.setDuration(FREEFORM_ANIMATION_DURATION);
-            final SurfaceControl.Transaction t = mTransactionSupplier.get();
-
-            // Get position of the task
-            final float x = mMoveToDesktopAnimator.getPosition().x;
-            final float y = mMoveToDesktopAnimator.getPosition().y;
-
-            animator.addUpdateListener(animation -> {
-                final float scale = (float) animation.getAnimatedValue();
-                t.setPosition(sc, x * (1 - scale), y * (1 - scale))
-                        .setScale(sc, scale, scale)
-                        .show(sc)
-                        .apply();
-            });
-            animator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    if (mOnAnimationFinishedCallback != null) {
-                        mOnAnimationFinishedCallback.accept(finishT);
-                    }
-                    mTransitions.getMainExecutor().execute(
-                            () -> finishCallback.onTransitionFinished(null, null));
-                }
-            });
-            animator.start();
-            return true;
+            return animateCancelDragToDesktopMode(change, startT, finishT, finishCallback,
+                    endBounds);
         }
 
         return false;
     }
 
+    private boolean animateMoveToDesktop(
+            @NonNull TransitionInfo.Change change,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        if (mDesktopModeWindowDecoration == null) {
+            Slog.e(TAG, "Window Decoration is not available for this transition");
+            return false;
+        }
+
+        final SurfaceControl leash = change.getLeash();
+        final Rect startBounds = change.getStartAbsBounds();
+        startT.setPosition(leash, startBounds.left, startBounds.right)
+                .setWindowCrop(leash, startBounds.width(), startBounds.height())
+                .show(leash);
+        mDesktopModeWindowDecoration.showResizeVeil(startT, startBounds);
+
+        final ValueAnimator animator = ValueAnimator.ofObject(new RectEvaluator(),
+                change.getStartAbsBounds(), change.getEndAbsBounds());
+        animator.setDuration(FREEFORM_ANIMATION_DURATION);
+        SurfaceControl.Transaction t = mTransactionSupplier.get();
+        animator.addUpdateListener(animation -> {
+            final Rect animationValue = (Rect) animator.getAnimatedValue();
+            t.setPosition(leash, animationValue.left, animationValue.right)
+                    .setWindowCrop(leash, animationValue.width(), animationValue.height())
+                    .show(leash);
+            mDesktopModeWindowDecoration.updateResizeVeil(t, animationValue);
+        });
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mDesktopModeWindowDecoration.hideResizeVeil();
+                mTransitions.getMainExecutor().execute(
+                        () -> finishCallback.onTransitionFinished(null, null));
+            }
+        });
+        animator.start();
+        return true;
+    }
+
+    private boolean animateStartDragToDesktopMode(
+            @NonNull TransitionInfo.Change change,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        // Transitioning to freeform but keeping fullscreen bounds, so the crop is set
+        // to null and we don't require an animation
+        final SurfaceControl sc = change.getLeash();
+        startT.setWindowCrop(sc, null);
+
+        if (mMoveToDesktopAnimator == null
+                || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
+            Slog.e(TAG, "No animator available for this transition");
+            return false;
+        }
+
+        // Calculate and set position of the task
+        final PointF position = mMoveToDesktopAnimator.getPosition();
+        startT.setPosition(sc, position.x, position.y);
+        finishT.setPosition(sc, position.x, position.y);
+
+        startT.apply();
+
+        mTransitions.getMainExecutor().execute(
+                () -> finishCallback.onTransitionFinished(null, null));
+
+        return true;
+    }
+
+    private boolean animateFinalizeDragToDesktopMode(
+            @NonNull TransitionInfo.Change change,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull Transitions.TransitionFinishCallback finishCallback,
+            @NonNull Rect endBounds) {
+        // This Transition animates a task to freeform bounds after being dragged into freeform
+        // mode and brings the remaining freeform tasks to front
+        final SurfaceControl sc = change.getLeash();
+        startT.setWindowCrop(sc, endBounds.width(),
+                endBounds.height());
+        startT.apply();
+
+        // End the animation that shrinks the window when task is first dragged from fullscreen
+        if (mMoveToDesktopAnimator != null) {
+            mMoveToDesktopAnimator.endAnimator();
+        }
+
+        // We want to find the scale of the current bounds relative to the end bounds. The
+        // task is currently scaled to DRAG_FREEFORM_SCALE and the final bounds will be
+        // scaled to FINAL_FREEFORM_SCALE. So, it is scaled to
+        // DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE relative to the freeform bounds
+        final ValueAnimator animator =
+                ValueAnimator.ofFloat(
+                        MoveToDesktopAnimator.DRAG_FREEFORM_SCALE / FINAL_FREEFORM_SCALE, 1f);
+        animator.setDuration(FREEFORM_ANIMATION_DURATION);
+        final SurfaceControl.Transaction t = mTransactionSupplier.get();
+        animator.addUpdateListener(animation -> {
+            final float animationValue = (float) animation.getAnimatedValue();
+            t.setScale(sc, animationValue, animationValue);
+
+            final float animationWidth = endBounds.width() * animationValue;
+            final float animationHeight = endBounds.height() * animationValue;
+            final int animationX = endBounds.centerX() - (int) (animationWidth / 2);
+            final int animationY = endBounds.centerY() - (int) (animationHeight / 2);
+
+            t.setPosition(sc, animationX, animationY);
+            t.apply();
+        });
+
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mOnAnimationFinishedCallback != null) {
+                    mOnAnimationFinishedCallback.accept(finishT);
+                }
+                mTransitions.getMainExecutor().execute(
+                        () -> finishCallback.onTransitionFinished(null, null));
+            }
+        });
+
+        animator.start();
+        return true;
+    }
+    private boolean animateCancelDragToDesktopMode(
+            @NonNull TransitionInfo.Change change,
+            @NonNull SurfaceControl.Transaction startT,
+            @NonNull SurfaceControl.Transaction finishT,
+            @NonNull Transitions.TransitionFinishCallback finishCallback,
+            @NonNull Rect endBounds) {
+        // This Transition animates a task to fullscreen after being dragged from the status
+        // bar and then released back into the status bar area
+        final SurfaceControl sc = change.getLeash();
+        // Hide the first (fullscreen) frame because the animation will start from the smaller
+        // scale size.
+        startT.hide(sc)
+                .setWindowCrop(sc, endBounds.width(), endBounds.height())
+                .apply();
+
+        if (mMoveToDesktopAnimator == null
+                || mMoveToDesktopAnimator.getTaskId() != change.getTaskInfo().taskId) {
+            Slog.e(TAG, "No animator available for this transition");
+            return false;
+        }
+
+        // End the animation that shrinks the window when task is first dragged from fullscreen
+        mMoveToDesktopAnimator.endAnimator();
+
+        final ValueAnimator animator = new ValueAnimator();
+        animator.setFloatValues(MoveToDesktopAnimator.DRAG_FREEFORM_SCALE, 1f);
+        animator.setDuration(FREEFORM_ANIMATION_DURATION);
+        final SurfaceControl.Transaction t = mTransactionSupplier.get();
+
+        // Get position of the task
+        final float x = mMoveToDesktopAnimator.getPosition().x;
+        final float y = mMoveToDesktopAnimator.getPosition().y;
+
+        animator.addUpdateListener(animation -> {
+            final float scale = (float) animation.getAnimatedValue();
+            t.setPosition(sc, x * (1 - scale), y * (1 - scale))
+                    .setScale(sc, scale, scale)
+                    .show(sc)
+                    .apply();
+        });
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mOnAnimationFinishedCallback != null) {
+                    mOnAnimationFinishedCallback.accept(finishT);
+                }
+                mTransitions.getMainExecutor().execute(
+                        () -> finishCallback.onTransitionFinished(null, null));
+            }
+        });
+        animator.start();
+        return true;
+    }
+
     @Nullable
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index e45dacf..e2dce88 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -168,6 +168,9 @@
     public static final int TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE =
             WindowManager.TRANSIT_FIRST_CUSTOM + 14;
 
+    /** Transition to animate task to desktop. */
+    public static final int TRANSIT_MOVE_TO_DESKTOP = WindowManager.TRANSIT_FIRST_CUSTOM + 15;
+
     private final WindowOrganizer mOrganizer;
     private final Context mContext;
     private final ShellExecutor mMainExecutor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index ea9976d..4cc755b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -222,7 +222,8 @@
                 && (info.getType() == Transitions.TRANSIT_FINALIZE_DRAG_TO_DESKTOP_MODE
                 || info.getType() == Transitions.TRANSIT_CANCEL_DRAG_TO_DESKTOP_MODE
                 || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
-                || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE)) {
+                || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
+                || info.getType() == Transitions.TRANSIT_MOVE_TO_DESKTOP)) {
             mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
                     .addTransitionPausingRelayout(transition);
         }
@@ -356,7 +357,8 @@
                     // App sometimes draws before the insets from WindowDecoration#relayout have
                     // been added, so they must be added here
                     mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
-                    mDesktopTasksController.get().moveToDesktop(mTaskId, wct);
+                    decoration.incrementRelayoutBlock();
+                    mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct);
                 }
                 decoration.closeHandleMenu();
             } else if (id == R.id.fullscreen_button) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index 5efa51b..421ad75 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -63,12 +63,8 @@
     }
 
     /** Checks that the visible region of [pipApp] window always moves down during the animation. */
-    @Presubmit
-    @Test
-    fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN)
+    @Presubmit @Test fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN)
 
     /** Checks that the visible region of [pipApp] layer always moves down during the animation. */
-    @Presubmit
-    @Test
-    fun pipLayerMovesDown() = pipLayerMoves(Direction.DOWN)
+    @Presubmit @Test fun pipLayerMovesDown() = pipLayerMoves(Direction.DOWN)
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
index 2494054..e37d806 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavLandscape.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
 import org.junit.Test
 
-class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
+open class CopyContentInSplitGesturalNavLandscape : CopyContentInSplit(Rotation.ROTATION_90) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
index 57943ec..2a50912 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/CopyContentInSplitGesturalNavPortrait.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.CopyContentInSplit
 import org.junit.Test
 
-class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
+open class CopyContentInSplitGesturalNavPortrait : CopyContentInSplit(Rotation.ROTATION_0) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
index 6f0e202..d5da1a8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavLandscape.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
 import org.junit.Test
 
-class DismissSplitScreenByDividerGesturalNavLandscape :
+open class DismissSplitScreenByDividerGesturalNavLandscape :
     DismissSplitScreenByDivider(Rotation.ROTATION_90) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
index dac8fa2..7fdcb9b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByDividerGesturalNavPortrait.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByDivider
 import org.junit.Test
 
-class DismissSplitScreenByDividerGesturalNavPortrait :
+open class DismissSplitScreenByDividerGesturalNavPortrait :
     DismissSplitScreenByDivider(Rotation.ROTATION_0) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
index baecc16..308e954 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavLandscape.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
 import org.junit.Test
 
-class DismissSplitScreenByGoHomeGesturalNavLandscape :
+open class DismissSplitScreenByGoHomeGesturalNavLandscape :
     DismissSplitScreenByGoHome(Rotation.ROTATION_90) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
index 3063ea5..39e75bd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DismissSplitScreenByGoHomeGesturalNavPortrait.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.DismissSplitScreenByGoHome
 import org.junit.Test
 
-class DismissSplitScreenByGoHomeGesturalNavPortrait :
+open class DismissSplitScreenByGoHomeGesturalNavPortrait :
     DismissSplitScreenByGoHome(Rotation.ROTATION_0) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
index 41660ba..e18da17 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavLandscape.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
 import org.junit.Test
 
-class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
+open class DragDividerToResizeGesturalNavLandscape : DragDividerToResize(Rotation.ROTATION_90) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
index c41ffb7..00d60e7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/DragDividerToResizeGesturalNavPortrait.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.DragDividerToResize
 import org.junit.Test
 
-class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
+open class DragDividerToResizeGesturalNavPortrait : DragDividerToResize(Rotation.ROTATION_0) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
index afde55b..d7efbc8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavLandscape.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
 import org.junit.Test
 
-class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
+open class EnterSplitScreenByDragFromAllAppsGesturalNavLandscape :
     EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_90) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
index 3765fc4..4eece3f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromAllAppsGesturalNavPortrait.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromAllApps
 import org.junit.Test
 
-class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
+open class EnterSplitScreenByDragFromAllAppsGesturalNavPortrait :
     EnterSplitScreenByDragFromAllApps(Rotation.ROTATION_0) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
index 1e128fd..d96b056 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavLandscape.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
 import org.junit.Test
 
-class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
+open class EnterSplitScreenByDragFromNotificationGesturalNavLandscape :
     EnterSplitScreenByDragFromNotification(Rotation.ROTATION_90) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
index 7767872..809b690 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromNotificationGesturalNavPortrait.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromNotification
 import org.junit.Test
 
-class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
+open class EnterSplitScreenByDragFromNotificationGesturalNavPortrait :
     EnterSplitScreenByDragFromNotification(Rotation.ROTATION_0) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
index 4ca4bd1..bbdf2d7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavLandscape.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
 import org.junit.Test
 
-class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
+open class EnterSplitScreenByDragFromShortcutGesturalNavLandscape :
     EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_90) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
index 2d9d258..5c29fd8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromShortcutGesturalNavPortrait.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromShortcut
 import org.junit.Test
 
-class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
+open class EnterSplitScreenByDragFromShortcutGesturalNavPortrait :
     EnterSplitScreenByDragFromShortcut(Rotation.ROTATION_0) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
index c9282ac..a7398eb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavLandscape.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
 import org.junit.Test
 
-class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
+open class EnterSplitScreenByDragFromTaskbarGesturalNavLandscape :
     EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_90) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
index 68c6d6c..eae88ad 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenByDragFromTaskbarGesturalNavPortrait.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenByDragFromTaskbar
 import org.junit.Test
 
-class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
+open class EnterSplitScreenByDragFromTaskbarGesturalNavPortrait :
     EnterSplitScreenByDragFromTaskbar(Rotation.ROTATION_0) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
index 304529e..7e8ee04 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavLandscape.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
 import org.junit.Test
 
-class EnterSplitScreenFromOverviewGesturalNavLandscape :
+open class EnterSplitScreenFromOverviewGesturalNavLandscape :
     EnterSplitScreenFromOverview(Rotation.ROTATION_90) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
index 53a6b44..9295c33 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/EnterSplitScreenFromOverviewGesturalNavPortrait.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.EnterSplitScreenFromOverview
 import org.junit.Test
 
-class EnterSplitScreenFromOverviewGesturalNavPortrait :
+open class EnterSplitScreenFromOverviewGesturalNavPortrait :
     EnterSplitScreenFromOverview(Rotation.ROTATION_0) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
index a467830..4b59e9f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavLandscape.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
 import org.junit.Test
 
-class SwitchAppByDoubleTapDividerGesturalNavLandscape :
+open class SwitchAppByDoubleTapDividerGesturalNavLandscape :
     SwitchAppByDoubleTapDivider(Rotation.ROTATION_90) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
index 1524233..5ff36d4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchAppByDoubleTapDividerGesturalNavPortrait.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchAppByDoubleTapDivider
 import org.junit.Test
 
-class SwitchAppByDoubleTapDividerGesturalNavPortrait :
+open class SwitchAppByDoubleTapDividerGesturalNavPortrait :
     SwitchAppByDoubleTapDivider(Rotation.ROTATION_0) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
index 0389659..c0cb721 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavLandscape.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
 import org.junit.Test
 
-class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
+open class SwitchBackToSplitFromAnotherAppGesturalNavLandscape :
     SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_90) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
index 7fadf18..8c14088 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromAnotherAppGesturalNavPortrait.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromAnotherApp
 import org.junit.Test
 
-class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
+open class SwitchBackToSplitFromAnotherAppGesturalNavPortrait :
     SwitchBackToSplitFromAnotherApp(Rotation.ROTATION_0) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
index 148cc52..7b6614b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavLandscape.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
 import org.junit.Test
 
-class SwitchBackToSplitFromHomeGesturalNavLandscape :
+open class SwitchBackToSplitFromHomeGesturalNavLandscape :
     SwitchBackToSplitFromHome(Rotation.ROTATION_90) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
index 0641fb0..5df5be9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromHomeGesturalNavPortrait.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromHome
 import org.junit.Test
 
-class SwitchBackToSplitFromHomeGesturalNavPortrait :
+open class SwitchBackToSplitFromHomeGesturalNavPortrait :
     SwitchBackToSplitFromHome(Rotation.ROTATION_0) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
index 741f871..9d63003 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
 import org.junit.Test
 
-class SwitchBackToSplitFromRecentGesturalNavLandscape :
+open class SwitchBackToSplitFromRecentGesturalNavLandscape :
     SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
index 778e2d6..9fa04b2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBackToSplitFromRecent
 import org.junit.Test
 
-class SwitchBackToSplitFromRecentGesturalNavPortrait :
+open class SwitchBackToSplitFromRecentGesturalNavPortrait :
     SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
index dcdca70..9386aa2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavLandscape.kt
@@ -22,7 +22,8 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
 import org.junit.Test
 
-class SwitchBetweenSplitPairsGesturalNavLandscape : SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
+open class SwitchBetweenSplitPairsGesturalNavLandscape :
+    SwitchBetweenSplitPairs(Rotation.ROTATION_90) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
index 3c69311..5ef2167 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/SwitchBetweenSplitPairsGesturalNavPortrait.kt
@@ -22,7 +22,8 @@
 import com.android.wm.shell.flicker.service.splitscreen.scenarios.SwitchBetweenSplitPairs
 import org.junit.Test
 
-class SwitchBetweenSplitPairsGesturalNavPortrait : SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
+open class SwitchBetweenSplitPairsGesturalNavPortrait :
+    SwitchBetweenSplitPairs(Rotation.ROTATION_0) {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
index c6566f5..9caab9b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -24,7 +24,7 @@
 import org.junit.runners.BlockJUnit4ClassRunner
 
 @RunWith(BlockJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
+open class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
index bb1a502..bf484e5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/platinum/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -24,7 +24,7 @@
 import org.junit.runners.BlockJUnit4ClassRunner
 
 @RunWith(BlockJUnit4ClassRunner::class)
-class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
+open class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
     @PlatinumTest(focusArea = "sysui")
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
index 91e7d47..e59ed64 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairsNoPip.kt
@@ -25,8 +25,8 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.PipAppHelper
-import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import com.android.wm.shell.flicker.splitscreen.benchmark.SplitScreenBase
+import com.android.wm.shell.flicker.utils.SplitScreenUtils
 import com.android.wm.shell.flicker.utils.layerBecomesInvisible
 import com.android.wm.shell.flicker.utils.splitAppLayerBoundsIsVisibleAtEnd
 import com.android.wm.shell.flicker.utils.splitAppLayerBoundsSnapToDivider
@@ -45,8 +45,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class SwitchBetweenSplitPairsNoPip (override val flicker: LegacyFlickerTest) :
-        SplitScreenBase(flicker) {
+class SwitchBetweenSplitPairsNoPip(override val flicker: LegacyFlickerTest) :
+    SplitScreenBase(flicker) {
 
     val thirdApp = SplitScreenUtils.getSendNotification(instrumentation)
     val pipApp = PipAppHelper(instrumentation)
@@ -77,74 +77,69 @@
             }
         }
 
-    /**
-     * Checks that [pipApp] window won't enter pip
-     */
+    /** Checks that [pipApp] window won't enter pip */
     @Presubmit
     @Test
     fun notEnterPip() {
         flicker.assertWm { isNotPinned(pipApp) }
     }
 
-    /**
-     * Checks the [pipApp] task did not reshow during transition.
-     */
+    /** Checks the [pipApp] task did not reshow during transition. */
     @Presubmit
     @Test
     fun app1WindowIsVisibleOnceApp2WindowIsInvisible() {
         flicker.assertLayers {
             this.isVisible(pipApp)
-                    .then().isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
-                    .then().isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
-                    .then().isInvisible(pipApp).isVisible(secondaryApp)
+                .then()
+                .isVisible(ComponentNameMatcher.LAUNCHER, isOptional = true)
+                .then()
+                .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true)
+                .then()
+                .isInvisible(pipApp)
+                .isVisible(secondaryApp)
         }
     }
 
     @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() =
-            flicker.splitAppLayerBoundsIsVisibleAtEnd(
-                    primaryApp,
-                    landscapePosLeft = tapl.isTablet,
-                    portraitPosTop = false
-            )
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
+            primaryApp,
+            landscapePosLeft = tapl.isTablet,
+            portraitPosTop = false
+        )
 
     @Presubmit
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() =
-            flicker.splitAppLayerBoundsIsVisibleAtEnd(
-                    secondaryApp,
-                    landscapePosLeft = !tapl.isTablet,
-                    portraitPosTop = true
-            )
+        flicker.splitAppLayerBoundsIsVisibleAtEnd(
+            secondaryApp,
+            landscapePosLeft = !tapl.isTablet,
+            portraitPosTop = true
+        )
 
-    /**
-     * Checks the [pipApp] task become invisible after transition finish.
-     */
-    @Presubmit
-    @Test
-    fun pipAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(pipApp)
+    /** Checks the [pipApp] task become invisible after transition finish. */
+    @Presubmit @Test fun pipAppLayerBecomesInvisible() = flicker.layerBecomesInvisible(pipApp)
 
-    /**
-     * Checks the [pipApp] task is in split screen bounds when transition start.
-     */
+    /** Checks the [pipApp] task is in split screen bounds when transition start. */
     @Presubmit
     @Test
     fun pipAppBoundsIsVisibleAtBegin() =
-            flicker.assertLayersStart {
-                this.splitAppLayerBoundsSnapToDivider(
-                        pipApp,
-                        landscapePosLeft = !tapl.isTablet,
-                        portraitPosTop = true,
-                        flicker.scenario.startRotation
-                )
-            }
+        flicker.assertLayersStart {
+            this.splitAppLayerBoundsSnapToDivider(
+                pipApp,
+                landscapePosLeft = !tapl.isTablet,
+                portraitPosTop = true,
+                flicker.scenario.startRotation
+            )
+        }
 
     companion object {
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
-        fun getParams() = LegacyFlickerTestFactory.nonRotationTests(
+        fun getParams() =
+            LegacyFlickerTestFactory.nonRotationTests(
                 supportedNavigationModes = listOf(NavBar.MODE_GESTURAL)
-        )
+            )
     }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
index 10dceba..4c44028 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
@@ -69,11 +69,12 @@
     @Test
     @Presubmit
     fun visibleLayersShownMoreThanOneConsecutiveEntry_withoutWallpaper() =
-        flicker.assertLayers { this.visibleLayersShownMoreThanOneConsecutiveEntry(
-            LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + listOf(
-                WALLPAPER_BBQ_WRAPPER
+        flicker.assertLayers {
+            this.visibleLayersShownMoreThanOneConsecutiveEntry(
+                LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS +
+                    listOf(WALLPAPER_BBQ_WRAPPER)
             )
-        ) }
+        }
 
     @Test
     fun splitScreenDividerIsVisibleAtEnd() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 1477cf7..5d87cf8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -57,6 +57,7 @@
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.After
@@ -92,6 +93,7 @@
     @Mock lateinit var mToggleResizeDesktopTaskTransitionHandler:
             ToggleResizeDesktopTaskTransitionHandler
     @Mock lateinit var launchAdjacentController: LaunchAdjacentController
+    @Mock lateinit var desktopModeWindowDecoration: DesktopModeWindowDecoration
 
     private lateinit var mockitoSession: StaticMockitoSession
     private lateinit var controller: DesktopTasksController
@@ -276,8 +278,8 @@
     fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() {
         val task = setUpFullscreenTask()
         task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
-        controller.moveToDesktop(task)
-        val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+        controller.moveToDesktop(desktopModeWindowDecoration, task)
+        val wct = getLatestMoveToDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
             .isEqualTo(WINDOWING_MODE_FREEFORM)
     }
@@ -286,15 +288,15 @@
     fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() {
         val task = setUpFullscreenTask()
         task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
-        controller.moveToDesktop(task)
-        val wct = getLatestWct(expectTransition = TRANSIT_CHANGE)
+        controller.moveToDesktop(desktopModeWindowDecoration, task)
+        val wct = getLatestMoveToDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
                 .isEqualTo(WINDOWING_MODE_UNDEFINED)
     }
 
     @Test
     fun moveToDesktop_nonExistentTask_doesNothing() {
-        controller.moveToDesktop(999)
+        controller.moveToDesktop(desktopModeWindowDecoration, 999)
         verifyWCTNotExecuted()
     }
 
@@ -305,9 +307,9 @@
         val fullscreenTask = setUpFullscreenTask()
         markTaskHidden(freeformTask)
 
-        controller.moveToDesktop(fullscreenTask)
+        controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTask)
 
-        with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+        with(getLatestMoveToDesktopWct()) {
             // Operations should include home task, freeform task
             assertThat(hierarchyOps).hasSize(3)
             assertReorderSequence(homeTask, freeformTask, fullscreenTask)
@@ -327,9 +329,9 @@
         val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
         markTaskHidden(freeformTaskSecond)
 
-        controller.moveToDesktop(fullscreenTaskDefault)
+        controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTaskDefault)
 
-        with(getLatestWct(expectTransition = TRANSIT_CHANGE)) {
+        with(getLatestMoveToDesktopWct()) {
             // Check that hierarchy operations do not include tasks from second display
             assertThat(hierarchyOps.map { it.container })
                 .doesNotContain(homeTaskSecond.token.asBinder())
@@ -498,6 +500,7 @@
     @Test
     fun handleRequest_fullscreenTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
         assumeTrue(ENABLE_SHELL_TRANSITIONS)
+        whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
 
         val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
         markTaskHidden(stashedFreeformTask)
@@ -569,6 +572,7 @@
     @Test
     fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() {
         assumeTrue(ENABLE_SHELL_TRANSITIONS)
+        whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
 
         val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY)
         markTaskHidden(stashedFreeformTask)
@@ -626,6 +630,8 @@
 
     @Test
     fun stashDesktopApps_stateUpdates() {
+        whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
+
         controller.stashDesktopApps(DEFAULT_DISPLAY)
 
         assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isTrue()
@@ -634,6 +640,8 @@
 
     @Test
     fun hideStashedDesktopApps_stateUpdates() {
+        whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true)
+
         desktopModeTaskRepository.setStashed(DEFAULT_DISPLAY, true)
         desktopModeTaskRepository.setStashed(SECOND_DISPLAY, true)
         controller.hideStashedDesktopApps(DEFAULT_DISPLAY)
@@ -715,6 +723,16 @@
         return arg.value
     }
 
+    private fun getLatestMoveToDesktopWct(): WindowContainerTransaction {
+        val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
+        if (ENABLE_SHELL_TRANSITIONS) {
+            verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
+        } else {
+            verify(shellTaskOrganizer).applyTransaction(arg.capture())
+        }
+        return arg.value
+    }
+
     private fun verifyWCTNotExecuted() {
         if (ENABLE_SHELL_TRANSITIONS) {
             verify(transitions, never()).startTransition(anyInt(), any(), isNull())
diff --git a/media/java/android/media/midi/MidiUmpDeviceService.java b/media/java/android/media/midi/MidiUmpDeviceService.java
index 0c6096e..6e2aaab 100644
--- a/media/java/android/media/midi/MidiUmpDeviceService.java
+++ b/media/java/android/media/midi/MidiUmpDeviceService.java
@@ -130,7 +130,7 @@
 
     /**
      * Returns the {@link MidiDeviceInfo} instance for this service
-     * @return the MidiDeviceInfo of the virtual MIDI device
+     * @return the MidiDeviceInfo of the virtual MIDI device if it was successfully created
      */
     public final @Nullable MidiDeviceInfo getDeviceInfo() {
         return mDeviceInfo;
@@ -140,7 +140,7 @@
      * Called to notify when the {@link MidiDeviceStatus} has changed
      * @param status the current status of the MIDI device
      */
-    public void onDeviceStatusChanged(@Nullable MidiDeviceStatus status) {
+    public void onDeviceStatusChanged(@NonNull MidiDeviceStatus status) {
     }
 
     /**
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 864a8bb..c8df760 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -123,8 +123,7 @@
 
     @Override
     public boolean performClick() {
-        mSwitch.performClick();
-        return super.performClick();
+        return mSwitch.performClick();
     }
 
     /**
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 79f8c46..7f5948c 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -22,7 +22,7 @@
     name: "SpaLib",
 
     srcs: ["src/**/*.kt"],
-
+    use_resource_processor: true,
     static_libs: [
         "androidx.slice_slice-builders",
         "androidx.slice_slice-core",
@@ -50,5 +50,6 @@
 // Expose the srcs to tests, so the tests can access the internal classes.
 filegroup {
     name: "SpaLib_srcs",
+    visibility: ["//frameworks/base/packages/SettingsLib/Spa/tests"],
     srcs: ["src/**/*.kt"],
 }
diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp
index e4d56cc..65f5d34 100644
--- a/packages/SettingsLib/Spa/testutils/Android.bp
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -22,7 +22,7 @@
     name: "SpaLibTestUtils",
 
     srcs: ["src/**/*.kt"],
-
+    use_resource_processor: true,
     static_libs: [
         "SpaLib",
         "androidx.arch.core_core-testing",
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index 4a7418f..eaeda3c 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -22,7 +22,7 @@
     name: "SpaPrivilegedLib",
 
     srcs: ["src/**/*.kt"],
-
+    use_resource_processor: true,
     static_libs: [
         "SpaLib",
         "SettingsLib",
@@ -45,5 +45,6 @@
 // Expose the srcs to tests, so the tests can access the internal classes.
 filegroup {
     name: "SpaPrivilegedLib_srcs",
+    visibility: [":__subpackages__"],
     srcs: ["src/**/*.kt"],
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/StringResources.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/StringResources.kt
new file mode 100644
index 0000000..05cb1b1
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/compose/StringResources.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.framework.compose
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.android.settingslib.R
+
+/** An empty placer holder string. */
+@Composable
+fun placeholder() = stringResource(R.string.summary_placeholder)
+
+/** Gets an empty placer holder string. */
+fun Context.getPlaceholder(): String = getString(R.string.summary_placeholder)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
index 1a7d896..de2cf1f 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
@@ -23,11 +23,11 @@
 import androidx.compose.runtime.State
 import androidx.compose.runtime.produceState
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
 import com.android.settingslib.Utils
 import com.android.settingslib.spa.framework.compose.rememberContext
 import com.android.settingslib.spaprivileged.R
 import com.android.settingslib.spaprivileged.framework.common.userManager
+import com.android.settingslib.spaprivileged.framework.compose.placeholder
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 
@@ -40,7 +40,7 @@
     @Composable
     fun produceLabel(app: ApplicationInfo, isClonedAppPage: Boolean = false): State<String> {
         val context = LocalContext.current
-        return produceState(initialValue = stringResource(R.string.summary_placeholder), app) {
+        return produceState(initialValue = placeholder(), app) {
             withContext(Dispatchers.IO) {
                 value = if (isClonedAppPage || isCloneApp(context, app)) {
                     context.getString(R.string.cloned_app_info_label, loadLabel(app))
@@ -82,7 +82,7 @@
             withContext(Dispatchers.IO) {
                 value = when {
                     context.userManager.isManagedProfile(app.userId) -> {
-                        context.getString(R.string.category_work)
+                        context.getString(com.android.settingslib.R.string.category_work)
                     }
 
                     else -> null
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
index fab3ae8..cc3584b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/EnterpriseRepository.kt
@@ -20,7 +20,7 @@
 import android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER
 import android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER
 import android.content.Context
-import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.R
 
 class EnterpriseRepository(private val context: Context) {
     private val resources by lazy {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index ae362c8..e2ff7b0 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -27,7 +27,7 @@
 import com.android.settingslib.RestrictedLockUtils
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
 import com.android.settingslib.RestrictedLockUtilsInternal
-import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.widget.R
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOn
@@ -56,11 +56,15 @@
 
     override fun getSummary(checked: Boolean?) = when (checked) {
         true -> enterpriseRepository.getEnterpriseString(
-            Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY, R.string.enabled_by_admin
+            updatableStringId = Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY,
+            resId = R.string.enabled_by_admin,
         )
+
         false -> enterpriseRepository.getEnterpriseString(
-            Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY, R.string.disabled_by_admin
+            updatableStringId = Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY,
+            resId = R.string.disabled_by_admin,
         )
+
         else -> ""
     }
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index b43210f..cee750e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -72,7 +72,7 @@
     private fun InstallType(app: ApplicationInfo) {
         if (!app.isInstantApp) return
         Spacer(modifier = Modifier.height(4.dp))
-        SettingsBody(stringResource(R.string.install_type_instant))
+        SettingsBody(stringResource(com.android.settingslib.widget.R.string.install_type_instant))
     }
 
     @Composable
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
index 3e96994..5fc1972 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
@@ -24,9 +24,8 @@
 import androidx.compose.runtime.State
 import androidx.compose.runtime.produceState
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.res.stringResource
-import com.android.settingslib.spaprivileged.R
 import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
+import com.android.settingslib.spaprivileged.framework.compose.placeholder
 import com.android.settingslib.spaprivileged.model.app.userHandle
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
@@ -36,7 +35,7 @@
 @Composable
 fun ApplicationInfo.getStorageSize(): State<String> {
     val context = LocalContext.current
-    return produceState(initialValue = stringResource(R.string.summary_placeholder)) {
+    return produceState(initialValue = placeholder()) {
         withContext(Dispatchers.IO) {
             val sizeBytes = calculateSizeBytes(context)
             value = if (sizeBytes != null) Formatter.formatFileSize(context, sizeBytes) else ""
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index cbc4822..1fa854a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -38,6 +38,7 @@
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
 import com.android.settingslib.spaprivileged.model.app.AppListModel
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.userId
@@ -173,7 +174,7 @@
         when (allowed.value) {
             true -> context.getString(R.string.app_permission_summary_allowed)
             false -> context.getString(R.string.app_permission_summary_not_allowed)
-            null -> context.getString(R.string.summary_placeholder)
+            null -> context.getPlaceholder()
         }
     }
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
index b08b6df..e77dcd4 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt
@@ -32,7 +32,7 @@
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.widget.preference.SwitchPreference
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
 import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted
 import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
 import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
@@ -72,9 +72,12 @@
         checked: State<Boolean?>,
     ): State<String> = when (restrictedMode) {
         is NoRestricted -> summaryIfNoRestricted
-        is BaseUserRestricted -> stateOf(context.getString(R.string.disabled))
+        is BaseUserRestricted -> stateOf(
+            context.getString(com.android.settingslib.R.string.disabled)
+        )
+
         is BlockedByAdmin -> derivedStateOf { restrictedMode.getSummary(checked.value) }
-        null -> stateOf(context.getString(R.string.summary_placeholder))
+        null -> stateOf(context.getPlaceholder())
     }
 }
 
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
index 26caa01..d11e63a 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
@@ -25,7 +25,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.testutils.delay
-import com.android.settingslib.spaprivileged.R
 import com.android.settingslib.spaprivileged.framework.common.userManager
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -66,7 +65,8 @@
 
         val contentDescription = produceIconContentDescription()
 
-        assertThat(contentDescription.value).isEqualTo(context.getString(R.string.category_work))
+        assertThat(contentDescription.value)
+            .isEqualTo(context.getString(com.android.settingslib.R.string.category_work))
     }
 
     @Test
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
index f6f4889..82fbee9 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
@@ -116,7 +116,7 @@
 
     private fun onMoreOptions() =
         composeTestRule.onNodeWithContentDescription(
-            context.getString(R.string.abc_action_menu_overflow_description)
+            context.getString(androidx.appcompat.R.string.abc_action_menu_overflow_description)
         )
 
     private companion object {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
index 961ec10..457b810 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
@@ -30,6 +30,7 @@
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.testutils.FakeNavControllerWrapper
 import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder
 import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
 import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
 import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
@@ -95,9 +96,7 @@
 
         val summaryState = getSummary(listModel)
 
-        assertThat(summaryState.value).isEqualTo(
-            context.getString(R.string.summary_placeholder)
-        )
+        assertThat(summaryState.value).isEqualTo(context.getPlaceholder())
     }
 
     @Test
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 1bd8355..969c148 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -56,6 +56,20 @@
       ]
     },
     {
+      "name": "SystemUIGoogleScreenshotTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "android.platform.test.annotations.Postsubmit"
+        }
+      ]
+    },
+    {
       // TODO(b/251476085): Consider merging with SystemUIGoogleScreenshotTests (in U+)
       "name": "SystemUIGoogleBiometricsScreenshotTests",
       "options": [
@@ -148,5 +162,21 @@
         }
       ]
     }
+  ],
+  "postsubmit": [
+   {
+      "name": "SystemUIGoogleScreenshotTests",
+      "options": [
+        {
+            "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+            "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        },
+        {
+            "include-annotation": "android.platform.test.annotations.Postsubmit"
+        }
+      ]
+    }
   ]
 }
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index de8287e..68fcdea 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1196,7 +1196,6 @@
     <dimen name="magnification_window_drag_corner_stroke">3dp</dimen>
     <!-- The extra padding to show the whole outer border -->
     <dimen name="magnifier_drag_handle_padding">3dp</dimen>
-    <dimen name="magnification_max_frame_size">300dp</dimen>
     <!-- Magnification settings panel -->
     <dimen name="magnification_setting_view_margin">24dp</dimen>
     <dimen name="magnification_setting_text_size">18sp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 6c8f8f3..4a31f3d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -124,7 +124,7 @@
     private float mScale;
 
     /**
-     * MagnificationFrame represents the bound of {@link #mMirrorSurface} and is constrained
+     * MagnificationFrame represents the bound of {@link #mMirrorSurfaceView} and is constrained
      * by the {@link #mMagnificationFrameBoundary}.
      * We use MagnificationFrame to calculate the position of {@link #mMirrorView}.
      * We combine MagnificationFrame with {@link #mMagnificationFrameOffsetX} and
@@ -225,6 +225,8 @@
     private boolean mAllowDiagonalScrolling = false;
     private boolean mEditSizeEnable = false;
     private boolean mSettingsPanelVisibility = false;
+    @VisibleForTesting
+    WindowMagnificationSizePrefs mWindowMagnificationSizePrefs;
 
     @Nullable
     private final MirrorWindowControl mMirrorWindowControl;
@@ -249,6 +251,7 @@
         mWindowMagnifierCallback = callback;
         mSysUiState = sysUiState;
         mConfiguration = new Configuration(context.getResources().getConfiguration());
+        mWindowMagnificationSizePrefs = new WindowMagnificationSizePrefs(mContext);
 
         final Display display = mContext.getDisplay();
         mDisplayId = mContext.getDisplayId();
@@ -272,8 +275,8 @@
                 com.android.internal.R.integer.config_shortAnimTime);
         updateDimensions();
 
-        final Size windowSize = getDefaultWindowSizeWithWindowBounds(mWindowBounds);
-        setMagnificationFrame(windowSize.getWidth(), windowSize.getHeight(),
+        final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible();
+        setMagnificationFrame(windowFrameSize.getWidth(), windowFrameSize.getHeight(),
                 mWindowBounds.width() / 2, mWindowBounds.height() / 2);
         computeBounceAnimationScale();
 
@@ -381,12 +384,16 @@
         if (!mMagnificationSizeScaleOptions.contains(index)) {
             return;
         }
-        final float scale = mMagnificationSizeScaleOptions.get(index, 1.0f);
-        final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
-        int size = (int) (initSize * scale);
+        int size = getMagnificationWindowSizeFromIndex(index);
         setWindowSize(size, size);
     }
 
+    int getMagnificationWindowSizeFromIndex(@MagnificationSize int index) {
+        final float scale = mMagnificationSizeScaleOptions.get(index, 1.0f);
+        int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3;
+        return (int) (initSize * scale) - (int) (initSize * scale) % 2;
+    }
+
     void setEditMagnifierSizeMode(boolean enable) {
         mEditSizeEnable = enable;
         applyResourcesValues();
@@ -395,6 +402,12 @@
             updateDimensions();
             applyTapExcludeRegion();
         }
+
+        if (!enable) {
+            // Keep the magnifier size when exiting edit mode
+            mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(
+                    new Size(mMagnificationFrame.width(), mMagnificationFrame.height()));
+        }
     }
 
     void setDiagonalScrolling(boolean enable) {
@@ -519,12 +532,12 @@
             return false;
         }
         mWindowBounds.set(currentWindowBounds);
-        final Size windowSize = getDefaultWindowSizeWithWindowBounds(mWindowBounds);
+        final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible();
         final float newCenterX = (getCenterX()) * mWindowBounds.width() / oldWindowBounds.width();
         final float newCenterY = (getCenterY()) * mWindowBounds.height() / oldWindowBounds.height();
 
-        setMagnificationFrame(windowSize.getWidth(), windowSize.getHeight(), (int) newCenterX,
-                (int) newCenterY);
+        setMagnificationFrame(windowFrameSize.getWidth(), windowFrameSize.getHeight(),
+                (int) newCenterX, (int) newCenterY);
         calculateMagnificationFrameBoundary();
         return true;
     }
@@ -738,6 +751,8 @@
     }
 
     private void setMagnificationFrame(int width, int height, int centerX, int centerY) {
+        mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(new Size(width, height));
+
         // Sets the initial frame area for the mirror and place it to the given center on the
         // display.
         final int initX = centerX - width / 2;
@@ -745,12 +760,18 @@
         mMagnificationFrame.set(initX, initY, initX + width, initY + height);
     }
 
-    private Size getDefaultWindowSizeWithWindowBounds(Rect windowBounds) {
-        int initSize = Math.min(windowBounds.width(), windowBounds.height()) / 2;
-        initSize = Math.min(mResources.getDimensionPixelSize(R.dimen.magnification_max_frame_size),
-                initSize);
-        initSize += 2 * mMirrorSurfaceMargin;
-        return new Size(initSize, initSize);
+    private Size restoreMagnificationWindowFrameSizeIfPossible() {
+        if (!mWindowMagnificationSizePrefs.isPreferenceSavedForCurrentDensity()) {
+            return getDefaultMagnificationWindowFrameSize();
+        }
+
+        return mWindowMagnificationSizePrefs.getSizeForCurrentDensity();
+    }
+
+    private Size getDefaultMagnificationWindowFrameSize() {
+        final int defaultSize = getMagnificationWindowSizeFromIndex(MagnificationSize.MEDIUM)
+                - 2 * mMirrorSurfaceMargin;
+        return new Size(defaultSize, defaultSize);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSizePrefs.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSizePrefs.java
new file mode 100644
index 0000000..4d7ad264
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSizePrefs.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Size;
+
+/**
+ * Class to handle SharedPreference for window magnification size.
+ */
+public final class WindowMagnificationSizePrefs {
+
+    private static final String WINDOW_MAGNIFICATION_PREFERENCES =
+            "window_magnification_preferences";
+    Context mContext;
+    SharedPreferences mWindowMagnificationSizePreferences;
+
+    public WindowMagnificationSizePrefs(Context context) {
+        mContext = context;
+        mWindowMagnificationSizePreferences = mContext
+                .getSharedPreferences(WINDOW_MAGNIFICATION_PREFERENCES, Context.MODE_PRIVATE);
+    }
+
+    /**
+     * Uses smallest screen width DP as the key for preference.
+     */
+    private String getKey() {
+        return String.valueOf(
+                mContext.getResources().getConfiguration().smallestScreenWidthDp);
+    }
+
+    /**
+     * Saves the window frame size for current screen density.
+     */
+    public void saveSizeForCurrentDensity(Size size) {
+        mWindowMagnificationSizePreferences.edit()
+                .putString(getKey(), size.toString()).apply();
+    }
+
+    /**
+     * Check if there is a preference saved for current screen density.
+     *
+     * @return true if there is a preference saved for current screen density, false if it is unset.
+     */
+    public boolean isPreferenceSavedForCurrentDensity() {
+        return mWindowMagnificationSizePreferences.contains(getKey());
+    }
+
+    /**
+     * Gets the size preference for current screen density.
+     */
+    public Size getSizeForCurrentDensity() {
+        return Size.parseSize(mWindowMagnificationSizePreferences.getString(getKey(), null));
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
index e60d4e1..0c7d56f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
@@ -100,6 +100,9 @@
             )
         } else if (newState == STATE_ERROR && oldState != STATE_ERROR) {
             animateIconOnce(R.drawable.face_dialog_dark_to_error)
+            iconView.contentDescription = context.getString(
+                    R.string.keyguard_face_failed
+            )
         } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
             animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
             iconView.contentDescription = context.getString(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index ebff0b0..39a45f7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -26,6 +26,7 @@
 
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -50,6 +51,7 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.util.Log;
+import android.view.HapticFeedbackConstants;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
@@ -234,6 +236,8 @@
     public static final VibrationEffect EFFECT_CLICK =
             VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
 
+    public static final int LONG_PRESS = HapticFeedbackConstants.LONG_PRESS;
+
     private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
         @Override
         public void onScreenTurnedOn() {
@@ -926,12 +930,24 @@
     @VisibleForTesting
     public void playStartHaptic() {
         if (mAccessibilityManager.isTouchExplorationEnabled()) {
-            mVibrator.vibrate(
-                    Process.myUid(),
-                    mContext.getOpPackageName(),
-                    EFFECT_CLICK,
-                    "udfps-onStart-click",
-                    UDFPS_VIBRATION_ATTRIBUTES);
+            if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+                if (mOverlay != null && mOverlay.getOverlayView() != null) {
+                    mVibrator.performHapticFeedback(
+                            mOverlay.getOverlayView(),
+                            HapticFeedbackConstants.CONTEXT_CLICK
+                    );
+                } else {
+                    Log.e(TAG, "No haptics played. Could not obtain overlay view to perform"
+                            + "vibration. Either the controller overlay is null or has no view");
+                }
+            } else {
+                mVibrator.vibrate(
+                        Process.myUid(),
+                        mContext.getOpPackageName(),
+                        EFFECT_CLICK,
+                        "udfps-onStart-click",
+                        UDFPS_VIBRATION_ATTRIBUTES);
+            }
         }
     }
 
@@ -1024,12 +1040,24 @@
             mKeyguardViewManager.showPrimaryBouncer(true);
 
             // play the same haptic as the LockIconViewController longpress
-            mVibrator.vibrate(
-                    Process.myUid(),
-                    mContext.getOpPackageName(),
-                    UdfpsController.EFFECT_CLICK,
-                    "aod-lock-icon-longpress",
-                    LOCK_ICON_VIBRATION_ATTRIBUTES);
+            if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+                if (mOverlay != null && mOverlay.getOverlayView() != null) {
+                    mVibrator.performHapticFeedback(
+                            mOverlay.getOverlayView(),
+                            UdfpsController.LONG_PRESS
+                    );
+                } else {
+                    Log.e(TAG, "No haptics played. Could not obtain overlay view to perform"
+                            + "vibration. Either the controller overlay is null or has no view");
+                }
+            } else {
+                mVibrator.vibrate(
+                        Process.myUid(),
+                        mContext.getOpPackageName(),
+                        UdfpsController.EFFECT_CLICK,
+                        "aod-lock-icon-longpress",
+                        LOCK_ICON_VIBRATION_ATTRIBUTES);
+            }
             return;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 9bbf1ef..b68b921 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -26,6 +26,7 @@
 import android.text.method.ScrollingMovementMethod
 import android.util.Log
 import android.view.View
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
 import android.view.accessibility.AccessibilityManager
 import android.widget.Button
 import android.widget.TextView
@@ -335,6 +336,13 @@
                 // dismiss prompt when authenticated and confirmed
                 launch {
                     viewModel.isAuthenticated.collect { authState ->
+                        // Disable background view for cancelling authentication once authenticated,
+                        // and remove from talkback
+                        if (authState.isAuthenticated) {
+                            backgroundView.setOnClickListener(null)
+                            backgroundView.importantForAccessibility =
+                                IMPORTANT_FOR_ACCESSIBILITY_NO
+                        }
                         if (authState.isAuthenticatedAndConfirmed) {
                             view.announceForAccessibility(
                                 view.resources.getString(R.string.biometric_dialog_authenticated)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index b387e4a..4c9dbe0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -40,8 +40,6 @@
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.controls.ui.ControlsActivity
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserTracker
 import java.util.concurrent.Executor
 import javax.inject.Inject
@@ -50,7 +48,6 @@
  * Activity for rearranging and removing controls for a given structure
  */
 open class ControlsEditingActivity @Inject constructor(
-    featureFlags: FeatureFlags,
     @Main private val mainExecutor: Executor,
     private val controller: ControlsControllerImpl,
     private val userTracker: UserTracker,
@@ -76,8 +73,6 @@
 
     private var isFromFavoriting: Boolean = false
 
-    private val isNewFlowEnabled: Boolean =
-        featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
     private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
         private val startingUser = controller.currentUserId
 
@@ -176,7 +171,7 @@
     private fun bindButtons() {
         addControls = requireViewById<Button>(R.id.addControls).apply {
             isEnabled = true
-            visibility = if (isNewFlowEnabled) View.VISIBLE else View.GONE
+            visibility = View.VISIBLE
             setOnClickListener {
                 if (saveButton.isEnabled) {
                     // The user has made changes
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 59fa7f5..23721c9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -33,7 +33,6 @@
 import android.widget.Button
 import android.widget.FrameLayout
 import android.widget.TextView
-import android.widget.Toast
 import android.window.OnBackInvokedCallback
 import android.window.OnBackInvokedDispatcher
 import androidx.activity.ComponentActivity
@@ -41,24 +40,19 @@
 import androidx.viewpager2.widget.ViewPager2
 import com.android.systemui.Prefs
 import com.android.systemui.R
-import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.TooltipManager
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.controls.ui.ControlsActivity
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserTracker
 import java.text.Collator
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
 open class ControlsFavoritingActivity @Inject constructor(
-    featureFlags: FeatureFlags,
     @Main private val executor: Executor,
     private val controller: ControlsControllerImpl,
-    private val listingController: ControlsListingController,
     private val userTracker: UserTracker,
 ) : ComponentActivity() {
 
@@ -92,7 +86,6 @@
     private lateinit var pageIndicator: ManagementPageIndicator
     private var mTooltipManager: TooltipManager? = null
     private lateinit var doneButton: View
-    private lateinit var otherAppsButton: View
     private lateinit var rearrangeButton: Button
     private var listOfStructures = emptyList<StructureContainer>()
 
@@ -104,8 +97,6 @@
         get() = openSource == EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR
     private val fromEditing: Boolean
         get() = openSource == EXTRA_SOURCE_VALUE_FROM_EDITING
-    private val isNewFlowEnabled: Boolean =
-        featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
     private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
         private val startingUser = controller.currentUserId
 
@@ -124,20 +115,6 @@
         onBackPressed()
     }
 
-    private val listingCallback = object : ControlsListingController.ControlsListingCallback {
-
-        override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
-            if (serviceInfos.size > 1) {
-                val newVisibility = if (isNewFlowEnabled) View.GONE else View.VISIBLE
-                if (otherAppsButton.visibility != newVisibility) {
-                    otherAppsButton.post {
-                        otherAppsButton.visibility = newVisibility
-                    }
-                }
-            }
-        }
-    }
-
     override fun onBackPressed() {
         if (fromEditing) {
             animateExitAndFinish()
@@ -342,7 +319,7 @@
                 getString(R.string.controls_favorite_rearrange_button)
             }
             isEnabled = false
-            visibility = if (isNewFlowEnabled) View.VISIBLE else View.GONE
+            visibility = View.VISIBLE
             setOnClickListener {
                 if (component == null) return@setOnClickListener
                 saveFavorites()
@@ -361,24 +338,6 @@
                 )
             }
         }
-        otherAppsButton = requireViewById<Button>(R.id.other_apps).apply {
-            setOnClickListener {
-                if (doneButton.isEnabled) {
-                    // The user has made changes
-                    Toast.makeText(
-                            applicationContext,
-                            R.string.controls_favorite_toast_no_changes,
-                            Toast.LENGTH_SHORT
-                            ).show()
-                }
-                startActivity(
-                    Intent(context, ControlsProviderSelectorActivity::class.java),
-                    ActivityOptions
-                        .makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle()
-                )
-                animateExitAndFinish()
-            }
-        }
 
         doneButton = requireViewById<Button>(R.id.done).apply {
             isEnabled = false
@@ -415,7 +374,6 @@
     override fun onStart() {
         super.onStart()
 
-        listingController.addCallback(listingCallback)
         userTracker.addCallback(userTrackerCallback, executor)
 
         if (DEBUG) {
@@ -440,7 +398,6 @@
     override fun onStop() {
         super.onStop()
 
-        listingController.removeCallback(listingCallback)
         userTracker.removeCallback(userTrackerCallback)
 
         if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 25eae20..16bb75d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -506,30 +506,22 @@
         val isPanel = selectedItem is SelectedItem.PanelItem
         val selectedStructure = (selectedItem as? SelectedItem.StructureItem)?.structure
                 ?: EMPTY_STRUCTURE
-        val newFlows = featureFlags.isEnabled(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS)
 
         val items = buildList {
             add(OverflowMenuAdapter.MenuItem(
                     context.getText(R.string.controls_open_app),
                     OPEN_APP_ID
             ))
-            if (newFlows || isPanel) {
-                if (extraApps) {
-                    add(OverflowMenuAdapter.MenuItem(
-                            context.getText(R.string.controls_menu_add_another_app),
-                            ADD_APP_ID
-                    ))
-                }
-                if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
-                    add(OverflowMenuAdapter.MenuItem(
-                            context.getText(R.string.controls_menu_remove),
-                            REMOVE_APP_ID,
-                    ))
-                }
-            } else {
+            if (extraApps) {
                 add(OverflowMenuAdapter.MenuItem(
-                        context.getText(R.string.controls_menu_add),
-                        ADD_CONTROLS_ID
+                        context.getText(R.string.controls_menu_add_another_app),
+                        ADD_APP_ID
+                ))
+            }
+            if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
+                add(OverflowMenuAdapter.MenuItem(
+                        context.getText(R.string.controls_menu_remove),
+                        REMOVE_APP_ID,
                 ))
             }
             if (!isPanel) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 7e4f501..90b27c7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -628,9 +628,6 @@
     // 2000 - device controls
     @JvmField val APP_PANELS_ALL_APPS_ALLOWED = releasedFlag(2001, "app_panels_all_apps_allowed")
 
-    @JvmField
-    val CONTROLS_MANAGEMENT_NEW_FLOWS = releasedFlag(2002, "controls_management_new_flows")
-
     // Enables removing app from Home control panel as a part of a new flow
     // TODO(b/269132640): Tracking Bug
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2686233..66de371 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3291,7 +3291,8 @@
                 flags |= StatusBarManager.DISABLE_RECENT;
             }
 
-            if (mPowerGestureIntercepted && mOccluded && isSecure()) {
+            if (mPowerGestureIntercepted && mOccluded && isSecure()
+                    && mUpdateMonitor.isFaceEnrolled()) {
                 flags |= StatusBarManager.DISABLE_RECENT;
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 3e7bdd1..9bb192b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -34,6 +34,7 @@
 import android.service.notification.ZenModeConfig;
 import android.service.quicksettings.Tile;
 import android.text.TextUtils;
+import android.util.Log;
 import android.view.View;
 import android.widget.Switch;
 
@@ -315,6 +316,7 @@
 
     private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
         public void onZenChanged(int zen) {
+            Log.d(TAG, "Zen changed to " + zen + ". Requesting refresh of tile.");
             refreshState(zen);
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 1c3a8850..dabdcc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -46,7 +46,6 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.Utils;
 import com.android.systemui.util.settings.GlobalSettings;
@@ -68,17 +67,17 @@
     private final Context mContext;
     private final UserTracker mUserTracker;
     private final BroadcastDispatcher mBroadcastDispatcher;
-    private final SettingObserver mModeSetting;
-    private final SettingObserver mConfigSetting;
     private final NotificationManager mNoMan;
     private final AlarmManager mAlarmManager;
     private final SetupObserver mSetupObserver;
     private final UserManager mUserManager;
+    private final GlobalSettings mGlobalSettings;
 
     private int mUserId;
     private boolean mRegistered;
     private ZenModeConfig mConfig;
-    private int mZenMode;
+    // This value is changed in the main thread, but may be read in a background thread.
+    private volatile int mZenMode;
     private long mZenUpdateTime;
     private NotificationManager.Policy mConsolidatedNotificationPolicy;
 
@@ -111,18 +110,20 @@
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
         mUserTracker = userTracker;
-        mModeSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE,
-                userTracker.getUserId()) {
+        mGlobalSettings = globalSettings;
+
+        ContentObserver modeContentObserver = new ContentObserver(handler) {
             @Override
-            protected void handleValueChanged(int value, boolean observedChange) {
+            public void onChange(boolean selfChange) {
+                int value = getModeSettingValueFromProvider();
+                Log.d(TAG, "Zen mode setting changed to " + value);
                 updateZenMode(value);
                 fireZenChanged(value);
             }
         };
-        mConfigSetting = new SettingObserver(globalSettings, handler, Global.ZEN_MODE_CONFIG_ETAG,
-                userTracker.getUserId()) {
+        ContentObserver configContentObserver = new ContentObserver(handler) {
             @Override
-            protected void handleValueChanged(int value, boolean observedChange) {
+            public void onChange(boolean selfChange) {
                 try {
                     Trace.beginSection("updateZenModeConfig");
                     updateZenModeConfig();
@@ -132,9 +133,9 @@
             }
         };
         mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
-        mModeSetting.setListening(true);
-        updateZenMode(mModeSetting.getValue());
-        mConfigSetting.setListening(true);
+        globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver);
+        updateZenMode(getModeSettingValueFromProvider());
+        globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG, configContentObserver);
         updateZenModeConfig();
         updateConsolidatedNotificationPolicy();
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
@@ -146,6 +147,10 @@
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
     }
 
+    private int getModeSettingValueFromProvider() {
+        return mGlobalSettings.getInt(Global.ZEN_MODE, /* default */ Global.ZEN_MODE_OFF);
+    }
+
     @Override
     public boolean isVolumeRestricted() {
         return mUserManager.hasUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME,
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
index 1e73cb3..1e65566 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
@@ -79,15 +79,15 @@
 
     private val hapticsScale: Float
         get() {
-            val intensityString = SystemProperties.get("persist.unfold.haptics_scale", "0.1")
-            return intensityString.toFloatOrNull() ?: 0.1f
+            val intensityString = SystemProperties.get("persist.unfold.haptics_scale", "0.5")
+            return intensityString.toFloatOrNull() ?: 0.5f
         }
 
     private val hapticsScaleTick: Float
         get() {
             val intensityString =
-                SystemProperties.get("persist.unfold.haptics_scale_end_tick", "0.6")
-            return intensityString.toFloatOrNull() ?: 0.6f
+                SystemProperties.get("persist.unfold.haptics_scale_end_tick", "1.0")
+            return intensityString.toFloatOrNull() ?: 1.0f
         }
 
     private val primitivesCount: Int
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 11ad206..a48fa5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -71,6 +71,7 @@
 import android.testing.TestableLooper;
 import android.testing.TestableResources;
 import android.text.TextUtils;
+import android.util.Size;
 import android.view.Display;
 import android.view.IWindowSession;
 import android.view.MotionEvent;
@@ -100,6 +101,7 @@
 import org.junit.After;
 import org.junit.Assume;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
@@ -313,10 +315,10 @@
         assertFalse(rects.isEmpty());
     }
 
+    @Ignore("The default window size should be constrained after fixing b/288056772")
     @Test
     public void enableWindowMagnification_LargeScreen_windowSizeIsConstrained() {
-        final int screenSize = mContext.getResources().getDimensionPixelSize(
-                R.dimen.magnification_max_frame_size) * 10;
+        final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
         mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
 
         mInstrumentation.runOnMainSync(() -> {
@@ -543,17 +545,22 @@
     }
 
     @Test
-    public void onScreenSizeChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() {
+    public void onScreenSizeAndDensityChanged_enabledAtTheCenterOfScreen_keepSameWindowSizeRatio() {
         // The default position is at the center of the screen.
         final float expectedRatio = 0.5f;
-        final Rect testWindowBounds = new Rect(
-                mWindowManager.getCurrentWindowMetrics().getBounds());
-        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
-                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
+
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
+
+        // Screen size and density change
+        mContext.getResources().getConfiguration().smallestScreenWidthDp =
+                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
+        final Rect testWindowBounds = new Rect(
+                mWindowManager.getCurrentWindowMetrics().getBounds());
+        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
         mWindowManager.setWindowBounds(testWindowBounds);
 
         mInstrumentation.runOnMainSync(() -> {
@@ -568,26 +575,49 @@
                 mWindowMagnificationController.getCenterY() / testWindowBounds.height(),
                 0);
     }
+
     @Test
-    public void screenSizeIsChangedToLarge_enabled_windowSizeIsConstrained() {
+    public void onScreenChangedToSavedDensity_enabled_restoreSavedMagnifierWindow() {
+        mContext.getResources().getConfiguration().smallestScreenWidthDp =
+                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
+        int windowFrameSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        mWindowMagnificationController.mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(
+                new Size(windowFrameSize, windowFrameSize));
+
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
                     Float.NaN);
         });
-        final int screenSize = mContext.getResources().getDimensionPixelSize(
-                R.dimen.magnification_max_frame_size) * 10;
+
+        WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView();
+        assertTrue(params.width == windowFrameSize);
+        assertTrue(params.height == windowFrameSize);
+    }
+
+    @Test
+    public void screenSizeIsChangedToLarge_enabled_defaultWindowSize() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+        });
+        final int screenSize = mWindowManager.getCurrentWindowMetrics().getBounds().width() * 10;
+        // Screen size and density change
+        mContext.getResources().getConfiguration().smallestScreenWidthDp =
+                mContext.getResources().getConfiguration().smallestScreenWidthDp * 2;
         mWindowManager.setWindowBounds(new Rect(0, 0, screenSize, screenSize));
 
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE);
         });
 
-        final int halfScreenSize = screenSize / 2;
+        final int defaultWindowSize =
+                mWindowMagnificationController.getMagnificationWindowSizeFromIndex(
+                        WindowMagnificationSettings.MagnificationSize.MEDIUM);
         WindowManager.LayoutParams params = mWindowManager.getLayoutParamsFromAttachedView();
-        // The frame size should be the half of smaller value of window height/width unless it
-        //exceed the max frame size.
-        assertTrue(params.width < halfScreenSize);
-        assertTrue(params.height < halfScreenSize);
+
+        assertTrue(params.width == defaultWindowSize);
+        assertTrue(params.height == defaultWindowSize);
     }
 
     @Test
@@ -1136,6 +1166,11 @@
         mWindowManager.setWindowInsets(testInsets);
     }
 
+    private int updateMirrorSurfaceMarginDimension() {
+        return mContext.getResources().getDimensionPixelSize(
+                R.dimen.magnification_mirror_surface_margin);
+    }
+
     @Surface.Rotation
     private int simulateRotateTheDevice() {
         final Display display = Mockito.spy(mContext.getDisplay());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
new file mode 100644
index 0000000..04b0d70
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Size;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class WindowMagnificationSizePrefsTest extends SysuiTestCase {
+
+    WindowMagnificationSizePrefs mWindowMagnificationSizePrefs =
+            new WindowMagnificationSizePrefs(mContext);
+
+    @Test
+    public void saveSizeForCurrentDensity_getExpectedSize() {
+        Size testSize = new Size(500, 500);
+        mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(testSize);
+
+        assertThat(mWindowMagnificationSizePrefs.getSizeForCurrentDensity())
+                .isEqualTo(testSize);
+    }
+
+    @Test
+    public void saveSizeForCurrentDensity_containsPreferenceForCurrentDensity() {
+        Size testSize = new Size(500, 500);
+        mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(testSize);
+
+        assertThat(mWindowMagnificationSizePrefs.isPreferenceSavedForCurrentDensity())
+                .isTrue();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 58982d1..7ab8e8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -23,6 +23,7 @@
 
 import static com.android.internal.util.FunctionalUtils.ThrowingConsumer;
 import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -61,6 +62,7 @@
 import android.os.VibrationAttributes;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.Pair;
+import android.view.HapticFeedbackConstants;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.Surface;
@@ -1128,6 +1130,36 @@
     }
 
     @Test
+    public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled_oneWayHapticsEnabled()
+            throws RemoteException {
+        when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true);
+        // Configure UdfpsView to accept the ACTION_DOWN event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+        // GIVEN that the overlay is showing and a11y touch exploration enabled
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        // WHEN ACTION_HOVER is received
+        verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture());
+        MotionEvent enterEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0);
+        mHoverListenerCaptor.getValue().onHover(mUdfpsView, enterEvent);
+        enterEvent.recycle();
+        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0);
+        mHoverListenerCaptor.getValue().onHover(mUdfpsView, moveEvent);
+        moveEvent.recycle();
+
+        // THEN context click haptic is played
+        verify(mVibrator).performHapticFeedback(
+                any(),
+                eq(HapticFeedbackConstants.CONTEXT_CLICK)
+        );
+    }
+
+    @Test
     public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled() throws RemoteException {
         // Configure UdfpsView to accept the ACTION_DOWN event
         when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
@@ -1160,6 +1192,35 @@
     }
 
     @Test
+    public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled__oneWayHapticsEnabled()
+            throws RemoteException {
+        when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true);
+        // Configure UdfpsView to accept the ACTION_DOWN event
+        when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+        // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
+        when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        // WHEN ACTION_DOWN is received
+        verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+        MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+        mBiometricExecutor.runAllReady();
+        downEvent.recycle();
+        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
+        mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+        mBiometricExecutor.runAllReady();
+        moveEvent.recycle();
+
+        // THEN NO haptic played
+        verify(mVibrator, never()).performHapticFeedback(any(), anyInt());
+    }
+
+    @Test
     public void onTouch_withoutNewTouchDetection_shouldCallOldFingerprintManagerPath()
             throws RemoteException {
         // Disable new touch detection.
@@ -1514,4 +1575,45 @@
         // THEN is fingerDown should be FALSE
         assertFalse(mUdfpsController.isFingerDown());
     }
+
+    @Test
+    public void playHaptic_onAodInterrupt_oneWayHapticsDisabled_onAcquiredBad_usesVibrate()
+            throws RemoteException {
+        // GIVEN UDFPS overlay is showing
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        // GIVEN there's been an AoD interrupt
+        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false);
+        mScreenObserver.onScreenTurnedOn();
+        mUdfpsController.onAodInterrupt(0, 0, 0, 0);
+
+        // THEN vibrate is used
+        verify(mVibrator).vibrate(
+                anyInt(),
+                anyString(),
+                eq(UdfpsController.EFFECT_CLICK),
+                eq("aod-lock-icon-longpress"),
+                eq(UdfpsController.LOCK_ICON_VIBRATION_ATTRIBUTES)
+        );
+    }
+
+    @Test
+    public void playHaptic_onAodInterrupt_oneWayHapticsEnabled_onAcquiredBad_performHapticFeedback()
+            throws RemoteException {
+        when(mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)).thenReturn(true);
+        // GIVEN UDFPS overlay is showing
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+        mFgExecutor.runAllReady();
+
+        // GIVEN there's been an AoD interrupt
+        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false);
+        mScreenObserver.onScreenTurnedOn();
+        mUdfpsController.onAodInterrupt(0, 0, 0, 0);
+
+        // THEN vibrate is used
+        verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
index 71d2ec1..bd3d09d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsEditingActivityTest.kt
@@ -16,8 +16,6 @@
 import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlsControllerImpl
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
@@ -46,7 +44,6 @@
     }
 
     private val uiExecutor = FakeExecutor(FakeSystemClock())
-    private val featureFlags = FakeFeatureFlags()
 
     @Mock lateinit var controller: ControlsControllerImpl
 
@@ -65,7 +62,6 @@
         ActivityTestRule(
             /* activityFactory= */ SingleActivityFactory {
                 TestableControlsEditingActivity(
-                    featureFlags,
                     uiExecutor,
                     controller,
                     userTracker,
@@ -81,8 +77,6 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, false)
     }
 
     @Test
@@ -101,16 +95,7 @@
     }
 
     @Test
-    fun testNewFlowDisabled_addControlsButton_gone() {
-        with(launchActivity()) {
-            val addControlsButton = requireViewById<Button>(R.id.addControls)
-            assertThat(addControlsButton.visibility).isEqualTo(View.GONE)
-        }
-    }
-
-    @Test
-    fun testNewFlowEnabled_addControlsButton_visible() {
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+    fun testAddControlsButton_visible() {
         with(launchActivity()) {
             val addControlsButton = requireViewById<Button>(R.id.addControls)
             assertThat(addControlsButton.visibility).isEqualTo(View.VISIBLE)
@@ -120,7 +105,6 @@
 
     @Test
     fun testNotLaunchFromFavoriting_saveButton_disabled() {
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
         with(launchActivity(isFromFavoriting = false)) {
             val saveButton = requireViewById<Button>(R.id.done)
             assertThat(saveButton.isEnabled).isFalse()
@@ -129,7 +113,6 @@
 
     @Test
     fun testLaunchFromFavoriting_saveButton_enabled() {
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
         with(launchActivity(isFromFavoriting = true)) {
             val saveButton = requireViewById<Button>(R.id.done)
             assertThat(saveButton.isEnabled).isTrue()
@@ -138,7 +121,6 @@
 
     @Test
     fun testNotFromFavoriting_addControlsPressed_launchesFavouriting() {
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
         with(launchActivity(isFromFavoriting = false)) {
             val addControls = requireViewById<Button>(R.id.addControls)
 
@@ -177,7 +159,6 @@
         )
 
     class TestableControlsEditingActivity(
-        featureFlags: FakeFeatureFlags,
         executor: FakeExecutor,
         controller: ControlsControllerImpl,
         userTracker: UserTracker,
@@ -186,7 +167,6 @@
         private val latch: CountDownLatch
     ) :
         ControlsEditingActivity(
-            featureFlags,
             executor,
             controller,
             userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
index f11c296..70d93a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsFavoritingActivityTest.kt
@@ -17,14 +17,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.activity.SingleActivityFactory
 import com.android.systemui.controls.ControlStatus
-import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.createLoadDataObject
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
@@ -73,8 +70,6 @@
 
     @Mock lateinit var controller: ControlsControllerImpl
 
-    @Mock lateinit var listingController: ControlsListingController
-
     @Mock lateinit var userTracker: UserTracker
 
     private var latch: CountDownLatch = CountDownLatch(1)
@@ -82,9 +77,6 @@
     @Mock private lateinit var mockDispatcher: OnBackInvokedDispatcher
     @Captor private lateinit var captureCallback: ArgumentCaptor<OnBackInvokedCallback>
     @Captor
-    private lateinit var listingCallback:
-        ArgumentCaptor<ControlsListingController.ControlsListingCallback>
-    @Captor
     private lateinit var controlsCallback: ArgumentCaptor<Consumer<ControlsController.LoadData>>
 
     @Rule
@@ -93,10 +85,8 @@
         ActivityTestRule(
             /* activityFactory= */ SingleActivityFactory {
                 TestableControlsFavoritingActivity(
-                    featureFlags,
                     executor,
                     controller,
-                    listingController,
                     userTracker,
                     mockDispatcher,
                     latch
@@ -109,7 +99,6 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, false)
     }
 
     // b/259549854 to root-cause and fix
@@ -130,14 +119,8 @@
     }
 
     @Test
-    fun testNewFlowEnabled_buttons() {
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
+    fun testButtons() {
         with(launchActivity()) {
-            verify(listingController).addCallback(listingCallback.capture())
-            listingCallback.value.onServicesUpdated(
-                listOf(mock(ControlsServiceInfo::class.java), mock(ControlsServiceInfo::class.java))
-            )
-
             val rearrangeButton = requireViewById<Button>(R.id.rearrange)
             assertThat(rearrangeButton.visibility).isEqualTo(View.VISIBLE)
             assertThat(rearrangeButton.isEnabled).isFalse()
@@ -149,36 +132,8 @@
     }
 
     @Test
-    fun testNewFlowDisabled_buttons() {
+    fun testRearrangePressed_savesAndlaunchesActivity() {
         with(launchActivity()) {
-            verify(listingController).addCallback(listingCallback.capture())
-            activityRule.runOnUiThread {
-                listingCallback.value.onServicesUpdated(
-                    listOf(
-                        mock(ControlsServiceInfo::class.java),
-                        mock(ControlsServiceInfo::class.java)
-                    )
-                )
-            }
-
-            val rearrangeButton = requireViewById<Button>(R.id.rearrange)
-            assertThat(rearrangeButton.visibility).isEqualTo(View.GONE)
-            assertThat(rearrangeButton.isEnabled).isFalse()
-
-            val otherAppsButton = requireViewById<Button>(R.id.other_apps)
-            otherAppsButton.waitForPost()
-            assertThat(otherAppsButton.visibility).isEqualTo(View.VISIBLE)
-        }
-    }
-
-    @Test
-    fun testNewFlowEnabled_rearrangePressed_savesAndlaunchesActivity() {
-        featureFlags.set(Flags.CONTROLS_MANAGEMENT_NEW_FLOWS, true)
-        with(launchActivity()) {
-            verify(listingController).addCallback(capture(listingCallback))
-            listingCallback.value.onServicesUpdated(
-                listOf(mock(ControlsServiceInfo::class.java), mock(ControlsServiceInfo::class.java))
-            )
             verify(controller).loadForComponent(any(), capture(controlsCallback), any())
             activityRule.runOnUiThread {
                 controlsCallback.value.accept(
@@ -224,19 +179,15 @@
         )
 
     class TestableControlsFavoritingActivity(
-        featureFlags: FeatureFlags,
         executor: Executor,
         controller: ControlsControllerImpl,
-        listingController: ControlsListingController,
         userTracker: UserTracker,
         private val mockDispatcher: OnBackInvokedDispatcher,
         private val latch: CountDownLatch
     ) :
         ControlsFavoritingActivity(
-            featureFlags,
             executor,
             controller,
-            listingController,
             userTracker,
         ) {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index c06dbdc..7f3d4b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -14,6 +14,7 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
@@ -24,10 +25,10 @@
 
 import android.app.NotificationManager;
 import android.os.Handler;
-import android.os.Looper;
 import android.provider.Settings;
 import android.service.notification.ZenModeConfig;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.test.filters.SmallTest;
@@ -45,6 +46,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -61,9 +65,10 @@
     DumpManager mDumpManager;
     @Mock
     UserTracker mUserTracker;
-
     private ZenModeControllerImpl mController;
 
+    private final FakeSettings mGlobalSettings = new FakeSettings();
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -72,10 +77,10 @@
 
         mController = new ZenModeControllerImpl(
                 mContext,
-                Handler.createAsync(Looper.myLooper()),
+                Handler.createAsync(TestableLooper.get(this).getLooper()),
                 mBroadcastDispatcher,
                 mDumpManager,
-                new FakeSettings(),
+                mGlobalSettings,
                 mUserTracker);
     }
 
@@ -131,4 +136,48 @@
         mController.addCallback(null);
         mController.fireConfigChanged(null);
     }
+
+    @Test
+    public void testModeChange() {
+        List<Integer> states = List.of(
+                Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                Settings.Global.ZEN_MODE_NO_INTERRUPTIONS,
+                Settings.Global.ZEN_MODE_ALARMS,
+                Settings.Global.ZEN_MODE_ALARMS
+        );
+
+        for (Integer state : states) {
+            mGlobalSettings.putInt(Settings.Global.ZEN_MODE, state);
+            TestableLooper.get(this).processAllMessages();
+            assertEquals(state.intValue(), mController.getZen());
+        }
+    }
+
+    @Test
+    public void testModeChange_callbackNotified() {
+        final AtomicInteger currentState = new AtomicInteger(-1);
+
+        ZenModeController.Callback callback = new Callback() {
+            @Override
+            public void onZenChanged(int zen) {
+                currentState.set(zen);
+            }
+        };
+
+        mController.addCallback(callback);
+
+        List<Integer> states = List.of(
+                Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                Settings.Global.ZEN_MODE_NO_INTERRUPTIONS,
+                Settings.Global.ZEN_MODE_ALARMS,
+                Settings.Global.ZEN_MODE_ALARMS
+        );
+
+        for (Integer state : states) {
+            mGlobalSettings.putInt(Settings.Global.ZEN_MODE, state);
+            TestableLooper.get(this).processAllMessages();
+            assertEquals(state.intValue(), currentState.get());
+        }
+
+    }
 }
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index f19f7f2..1741593 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -19,12 +19,10 @@
 import android.app.ActivityManager;
 import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.Sensor;
@@ -40,8 +38,6 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
 import android.provider.Settings;
 import android.util.MutableBoolean;
 import android.util.Slog;
@@ -113,19 +109,6 @@
      */
     private static final int CAMERA_POWER_TAP_COUNT_THRESHOLD = 2;
 
-    /** Action for starting emergency alerts on Wear OS. */
-    private static final String WEAR_LAUNCH_EMERGENCY_ACTION =
-            "com.android.systemui.action.LAUNCH_EMERGENCY";
-
-    /** Action for starting emergency alerts in retail mode on Wear OS. */
-    private static final String WEAR_LAUNCH_EMERGENCY_RETAIL_ACTION =
-            "com.android.systemui.action.LAUNCH_EMERGENCY_RETAIL";
-
-    /**
-     * Boolean extra for distinguishing intents coming from power button gesture.
-     */
-    private static final String EXTRA_LAUNCH_EMERGENCY_VIA_GESTURE = "launch_emergency_via_gesture";
-
     /** The listener that receives the gesture event. */
     private final GestureEventListener mGestureListener = new GestureEventListener();
     private final CameraLiftTriggerEventListener mCameraLiftTriggerListener =
@@ -198,7 +181,6 @@
     private final UiEventLogger mUiEventLogger;
 
     private boolean mHasFeatureWatch;
-    private long mVibrateMilliSecondsForPanicGesture;
 
     @VisibleForTesting
     public enum GestureLauncherEvent implements UiEventLogger.UiEventEnum {
@@ -268,13 +250,6 @@
 
             mHasFeatureWatch =
                     mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
-            mVibrateMilliSecondsForPanicGesture =
-                    resources.getInteger(
-                            com.android
-                                    .internal
-                                    .R
-                                    .integer
-                                    .config_mashPressVibrateTimeOnPowerButton);
         }
     }
 
@@ -714,11 +689,6 @@
                         userSetupComplete));
             }
 
-            if (mHasFeatureWatch) {
-                onEmergencyGestureDetectedOnWatch();
-                return true;
-            }
-
             StatusBarManagerInternal service = LocalServices.getService(
                     StatusBarManagerInternal.class);
             service.onEmergencyActionLaunchGestureDetected();
@@ -728,37 +698,6 @@
         }
     }
 
-    private void onEmergencyGestureDetectedOnWatch() {
-        Intent emergencyIntent =
-                new Intent(
-                        isInRetailMode()
-                                ? WEAR_LAUNCH_EMERGENCY_RETAIL_ACTION
-                                : WEAR_LAUNCH_EMERGENCY_ACTION);
-        PackageManager pm = mContext.getPackageManager();
-        ResolveInfo resolveInfo = pm.resolveActivity(emergencyIntent, /*flags=*/0);
-        if (resolveInfo == null) {
-            Slog.w(TAG, "Couldn't find an app to process the emergency intent "
-                    + emergencyIntent.getAction());
-            return;
-        }
-
-        Vibrator vibrator = mContext.getSystemService(Vibrator.class);
-        vibrator.vibrate(VibrationEffect.createOneShot(mVibrateMilliSecondsForPanicGesture,
-                VibrationEffect.DEFAULT_AMPLITUDE));
-
-        emergencyIntent.setComponent(
-                new ComponentName(
-                        resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name));
-        emergencyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        emergencyIntent.putExtra(EXTRA_LAUNCH_EMERGENCY_VIA_GESTURE, true);
-        mContext.startActivityAsUser(emergencyIntent, new UserHandle(mUserId));
-    }
-
-    private boolean isInRetailMode() {
-        return Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.DEVICE_DEMO_MODE, 0) == 1;
-    }
-
     private boolean isUserSetupComplete() {
         return Settings.Secure.getIntForUser(mContext.getContentResolver(),
                 Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index dc83125..383bb25 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -6434,6 +6434,7 @@
         }
         updateServiceConnectionActivitiesLocked(psr);
         psr.removeAllConnections();
+        psr.removeAllSdkSandboxConnections();
 
         psr.mAllowlistManager = false;
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index bef53c7..faf1900a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -22,6 +22,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY;
+import static com.android.server.am.BroadcastConstants.getDeviceConfigBoolean;
 
 import android.annotation.NonNull;
 import android.app.ActivityThread;
@@ -153,6 +154,11 @@
     static final String KEY_TIERED_CACHED_ADJ_DECAY_TIME = "tiered_cached_adj_decay_time";
     static final String KEY_USE_MODERN_TRIM = "use_modern_trim";
 
+    /**
+     * Whether or not to enable the new oom adjuster implementation.
+     */
+    static final String KEY_ENABLE_NEW_OOMADJ = "enable_new_oom_adj";
+
     private static final int DEFAULT_MAX_CACHED_PROCESSES = 1024;
     private static final boolean DEFAULT_PRIORITIZE_ALARM_BROADCASTS = true;
     private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
@@ -216,6 +222,11 @@
     private static final boolean DEFAULT_USE_MODERN_TRIM = true;
 
     /**
+     * The default value to {@link #KEY_ENABLE_NEW_OOMADJ}.
+     */
+    private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = false;
+
+    /**
      * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
      */
     private static final int
@@ -1051,6 +1062,9 @@
     /** @see #KEY_USE_MODERN_TRIM */
     public boolean USE_MODERN_TRIM = DEFAULT_USE_MODERN_TRIM;
 
+    /** @see #KEY_ENABLE_NEW_OOMADJ */
+    public boolean ENABLE_NEW_OOMADJ = DEFAULT_ENABLE_NEW_OOM_ADJ;
+
     private final OnPropertiesChangedListener mOnDeviceConfigChangedListener =
             new OnPropertiesChangedListener() {
                 @Override
@@ -1308,6 +1322,7 @@
         CUR_TRIM_CACHED_PROCESSES = (Integer.min(CUR_MAX_CACHED_PROCESSES, MAX_CACHED_PROCESSES)
                     - rawMaxEmptyProcesses) / 3;
 
+        loadNativeBootDeviceConfigConstants();
     }
 
     public void start(ContentResolver resolver) {
@@ -1347,6 +1362,11 @@
                         DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS));
     }
 
+    private void loadNativeBootDeviceConfigConstants() {
+        ENABLE_NEW_OOMADJ = getDeviceConfigBoolean(KEY_ENABLE_NEW_OOMADJ,
+                DEFAULT_ENABLE_NEW_OOM_ADJ);
+    }
+
     public void setOverrideMaxCachedProcesses(int value) {
         mOverrideMaxCachedProcesses = value;
         updateMaxCachedProcesses();
@@ -1997,6 +2017,13 @@
             DEFAULT_USE_MODERN_TRIM);
     }
 
+    private void updateEnableNewOomAdj() {
+        ENABLE_NEW_OOMADJ = DeviceConfig.getBoolean(
+            DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
+            KEY_ENABLE_NEW_OOMADJ,
+            DEFAULT_ENABLE_NEW_OOM_ADJ);
+    }
+
     private void updateFGSPermissionEnforcementFlagsIfNecessary(@NonNull String name) {
         ForegroundServiceTypePolicy.getDefaultPolicy()
                 .updatePermissionEnforcementFlagIfNecessary(name);
@@ -2187,6 +2214,9 @@
         pw.print("  "); pw.print(KEY_TIERED_CACHED_ADJ_DECAY_TIME);
         pw.print("="); pw.println(TIERED_CACHED_ADJ_DECAY_TIME);
 
+        pw.print("  "); pw.print(KEY_ENABLE_NEW_OOMADJ);
+        pw.print("="); pw.println(ENABLE_NEW_OOMADJ);
+
         pw.println();
         if (mOverrideMaxCachedProcesses >= 0) {
             pw.print("  mOverrideMaxCachedProcesses="); pw.println(mOverrideMaxCachedProcesses);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a0fae26..c1f2f67 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2029,6 +2029,7 @@
                 app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
                 app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM);
                 addPidLocked(app);
+                mOomAdjuster.onProcessBeginLocked(app);
                 updateLruProcessLocked(app, false, null);
                 updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
             }
@@ -2422,7 +2423,9 @@
         mProcessList.init(this, activeUids, mPlatformCompat);
         mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(), null);
         mPhantomProcessList = new PhantomProcessList(this);
-        mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids, handlerThread);
+        mOomAdjuster = mConstants.ENABLE_NEW_OOMADJ
+                ? new OomAdjusterModernImpl(this, mProcessList, activeUids, handlerThread)
+                : new OomAdjuster(this, mProcessList, activeUids, handlerThread);
 
         mIntentFirewall = null;
         mProcessStats = new ProcessStatsService(this, mContext.getCacheDir());
@@ -2483,7 +2486,9 @@
         mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(),
                 new LowMemDetector(this));
         mPhantomProcessList = new PhantomProcessList(this);
-        mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids);
+        mOomAdjuster = mConstants.ENABLE_NEW_OOMADJ
+                ? new OomAdjusterModernImpl(this, mProcessList, activeUids)
+                : new OomAdjuster(this, mProcessList, activeUids);
 
         // Broadcast policy parameters
         final BroadcastConstants foreConstants = new BroadcastConstants(
@@ -4595,6 +4600,7 @@
         EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
 
         synchronized (mProcLock) {
+            mOomAdjuster.onProcessBeginLocked(app);
             mOomAdjuster.setAttachingProcessStatesLSP(app);
             clearProcessForegroundLocked(app);
             app.setDebugging(false);
@@ -6980,6 +6986,7 @@
                     sdkSandboxClientAppPackage,
                     new HostingRecord(HostingRecord.HOSTING_TYPE_ADDED_APPLICATION,
                             customProcess != null ? customProcess : info.processName));
+            mOomAdjuster.onProcessBeginLocked(app);
             updateLruProcessLocked(app, false, null);
             updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
         }
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 8c1fd51..2fff79b 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -373,7 +373,7 @@
      * Return the {@link SystemProperty} name for the given key in our
      * {@link DeviceConfig} namespace.
      */
-    private @NonNull String propertyFor(@NonNull String key) {
+    private static @NonNull String propertyFor(@NonNull String key) {
         return "persist.device_config." + NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT + "." + key;
     }
 
@@ -382,11 +382,11 @@
      * {@link DeviceConfig} namespace, but with a different prefix that can be
      * used to locally override the {@link DeviceConfig} value.
      */
-    private @NonNull String propertyOverrideFor(@NonNull String key) {
+    private static @NonNull String propertyOverrideFor(@NonNull String key) {
         return "persist.sys." + NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT + "." + key;
     }
 
-    private boolean getDeviceConfigBoolean(@NonNull String key, boolean def) {
+    static boolean getDeviceConfigBoolean(@NonNull String key, boolean def) {
         return SystemProperties.getBoolean(propertyOverrideFor(key),
                 SystemProperties.getBoolean(propertyFor(key), def));
     }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 459c6ff..1f9e89e 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -41,6 +41,7 @@
 import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
 import static android.app.ActivityManager.PROCESS_STATE_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
 import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
@@ -124,6 +125,7 @@
 import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal.OomAdjReason;
@@ -369,20 +371,21 @@
      */
     private final Handler mProcessGroupHandler;
 
-    private final int[] mTmpSchedGroup = new int[1];
+    protected final int[] mTmpSchedGroup = new int[1];
 
-    private final ActivityManagerService mService;
-    private final ProcessList mProcessList;
-    private final ActivityManagerGlobalLock mProcLock;
+    final ActivityManagerService mService;
+    final ProcessList mProcessList;
+    final ActivityManagerGlobalLock mProcLock;
 
     private final int mNumSlots;
-    private final ArrayList<ProcessRecord> mTmpProcessList = new ArrayList<ProcessRecord>();
-    private final ArrayList<UidRecord> mTmpBecameIdle = new ArrayList<UidRecord>();
-    private final ActiveUids mTmpUidRecords;
-    private final ArrayDeque<ProcessRecord> mTmpQueue;
-    private final ArraySet<ProcessRecord> mTmpProcessSet = new ArraySet<>();
-    private final ArraySet<ProcessRecord> mPendingProcessSet = new ArraySet<>();
-    private final ArraySet<ProcessRecord> mProcessesInCycle = new ArraySet<>();
+    protected final ArrayList<ProcessRecord> mTmpProcessList = new ArrayList<ProcessRecord>();
+    protected final ArrayList<ProcessRecord> mTmpProcessList2 = new ArrayList<ProcessRecord>();
+    protected final ArrayList<UidRecord> mTmpBecameIdle = new ArrayList<UidRecord>();
+    protected final ActiveUids mTmpUidRecords;
+    protected final ArrayDeque<ProcessRecord> mTmpQueue;
+    protected final ArraySet<ProcessRecord> mTmpProcessSet = new ArraySet<>();
+    protected final ArraySet<ProcessRecord> mPendingProcessSet = new ArraySet<>();
+    protected final ArraySet<ProcessRecord> mProcessesInCycle = new ArraySet<>();
 
     /**
      * Flag to mark if there is an ongoing oomAdjUpdate: potentially the oomAdjUpdate
@@ -412,7 +415,7 @@
         this(service, processList, activeUids, createAdjusterThread());
     }
 
-    private static ServiceThread createAdjusterThread() {
+    static ServiceThread createAdjusterThread() {
         // The process group is usually critical to the response time of foreground app, so the
         // setter should apply it as soon as possible.
         final ServiceThread adjusterThread =
@@ -532,7 +535,7 @@
         mPendingProcessSet.remove(app);
 
         mProcessesInCycle.clear();
-        computeOomAdjLSP(app, cachedAdj, topApp, false, now, false, true);
+        computeOomAdjLSP(app, cachedAdj, topApp, false, now, false, true, oomAdjReason, true);
         if (!mProcessesInCycle.isEmpty()) {
             // We can't use the score here if there is a cycle, abort.
             for (int i = mProcessesInCycle.size() - 1; i >= 0; i--) {
@@ -550,7 +553,7 @@
                     && (uidRec.getSetProcState() != uidRec.getCurProcState()
                     || uidRec.getSetCapability() != uidRec.getCurCapability()
                     || uidRec.isSetAllowListed() != uidRec.isCurAllowListed())) {
-                ActiveUids uids = mTmpUidRecords;
+                final ActiveUids uids = mTmpUidRecords;
                 uids.clear();
                 uids.put(uidRec.getUid(), uidRec);
                 updateUidsLSP(uids, SystemClock.elapsedRealtime());
@@ -633,19 +636,20 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    private boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+    protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
         final ProcessRecord topApp = mService.getTopApp();
 
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
         mService.mOomAdjProfiler.oomAdjStarted();
         mAdjSeq++;
 
-        // Firstly, try to see if the importance of itself gets changed
         final ProcessStateRecord state = app.mState;
         final boolean wasCached = state.isCached();
         final int oldAdj = state.getCurRawAdj();
         final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ
                 ? oldAdj : UNKNOWN_ADJ;
+
+        // Firstly, try to see if the importance of itself gets changed
         final boolean wasBackground = ActivityManager.isProcStateBackground(
                 state.getSetProcState());
         final int oldCap = state.getSetCapability();
@@ -693,8 +697,6 @@
         mPendingProcessSet.clear();
 
         if (!containsCycle) {
-            // Reset the flag
-            state.setReachable(false);
             // Remove this app from the return list because we've done the computation on it.
             processes.remove(app);
         }
@@ -718,8 +720,13 @@
         return true;
     }
 
+    /**
+     * Collect the reachable processes from the given {@code apps}, the result will be
+     * returned in the given {@code processes}, which will include the processes from
+     * the given {@code apps}.
+     */
     @GuardedBy("mService")
-    private boolean collectReachableProcessesLocked(ArraySet<ProcessRecord> apps,
+    protected boolean collectReachableProcessesLocked(ArraySet<ProcessRecord> apps,
             ArrayList<ProcessRecord> processes, ActiveUids uids) {
         final ArrayDeque<ProcessRecord> queue = mTmpQueue;
         queue.clear();
@@ -824,11 +831,15 @@
         if (size > 0) {
             // Reverse the process list, since the updateOomAdjInnerLSP scans from the end of it.
             for (int l = 0, r = size - 1; l < r; l++, r--) {
-                ProcessRecord t = processes.get(l);
-                processes.set(l, processes.get(r));
+                final ProcessRecord t = processes.get(l);
+                final ProcessRecord u = processes.get(r);
+                t.mState.setReachable(false);
+                u.mState.setReachable(false);
+                processes.set(l, u);
                 processes.set(r, t);
             }
         }
+
         return containsCycle;
     }
 
@@ -928,24 +939,18 @@
      * Update OomAdj for all processes within the given list (could be partial), or the whole LRU
      * list if the given list is null; when it's partial update, each process's client proc won't
      * get evaluated recursively here.
+     *
+     * <p>Note: If the given {@code processes} is not null, the expectation to it is, the caller
+     * must have called {@link collectReachableProcessesLocked} on it.
      */
     @GuardedBy({"mService", "mProcLock"})
-    private void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
+    protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
             ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
             boolean startProfiling) {
-        if (startProfiling) {
-            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
-            mService.mOomAdjProfiler.oomAdjStarted();
-        }
-        final long now = SystemClock.uptimeMillis();
-        final long nowElapsed = SystemClock.elapsedRealtime();
-        final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
         final boolean fullUpdate = processes == null;
+        final ArrayList<ProcessRecord> activeProcesses = fullUpdate
+                ? mProcessList.getLruProcessesLOSP() : processes;
         ActiveUids activeUids = uids;
-        ArrayList<ProcessRecord> activeProcesses = fullUpdate ? mProcessList.getLruProcessesLOSP()
-                : processes;
-        final int numProc = activeProcesses.size();
-
         if (activeUids == null) {
             final int numUids = mActiveUids.size();
             activeUids = mTmpUidRecords;
@@ -956,14 +961,14 @@
             }
         }
 
-        // Reset state in all uid records.
-        for (int  i = activeUids.size() - 1; i >= 0; i--) {
-            final UidRecord uidRec = activeUids.valueAt(i);
-            if (DEBUG_UID_OBSERVERS) {
-                Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
-            }
-            uidRec.reset();
+        if (startProfiling) {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
+            mService.mOomAdjProfiler.oomAdjStarted();
         }
+        final long now = SystemClock.uptimeMillis();
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
+        final int numProc = activeProcesses.size();
 
         mAdjSeq++;
         if (fullUpdate) {
@@ -971,6 +976,9 @@
             mNewNumAServiceProcs = 0;
         }
 
+        // Reset state in all uid records.
+        resetUidRecordsLsp(activeUids);
+
         boolean retryCycles = false;
         boolean computeClients = fullUpdate || potentialCycles;
 
@@ -996,8 +1004,9 @@
             if (!app.isKilledByAm() && app.getThread() != null) {
                 state.setProcStateChanged(false);
                 app.mOptRecord.setLastOomAdjChangeReason(oomAdjReason);
+                // It won't enter cycle if not computing clients.
                 computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, fullUpdate, now, false,
-                        computeClients); // It won't enter cycle if not computing clients.
+                        computeClients, oomAdjReason, true);
                 // if any app encountered a cycle, we need to perform an additional loop later
                 retryCycles |= state.containsCycle();
                 // Keep the completedAdjSeq to up to date.
@@ -1034,7 +1043,7 @@
                     final ProcessStateRecord state = app.mState;
                     if (!app.isKilledByAm() && app.getThread() != null && state.containsCycle()) {
                         if (computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, true, now,
-                                true, true)) {
+                                true, true, oomAdjReason, true)) {
                             retryCycles = true;
                         }
                     }
@@ -1045,10 +1054,33 @@
 
         assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
 
+        postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
+
+        if (startProfiling) {
+            mService.mOomAdjProfiler.oomAdjEnded();
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        }
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    private void resetUidRecordsLsp(@NonNull ActiveUids activeUids) {
+        // Reset state in all uid records.
+        for (int  i = activeUids.size() - 1; i >= 0; i--) {
+            final UidRecord uidRec = activeUids.valueAt(i);
+            if (DEBUG_UID_OBSERVERS) {
+                Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
+            }
+            uidRec.reset();
+        }
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    protected void postUpdateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, ActiveUids activeUids,
+            long now, long nowElapsed, long oldTime) {
         mNumNonCachedProcs = 0;
         mNumCachedHiddenProcs = 0;
 
-        boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids,
+        final boolean allChanged = updateAndTrimProcessLSP(now, nowElapsed, oldTime, activeUids,
                 oomAdjReason);
         mNumServiceProcs = mNewNumServiceProcs;
 
@@ -1085,14 +1117,10 @@
                 Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms");
             }
         }
-        if (startProfiling) {
-            mService.mOomAdjProfiler.oomAdjEnded();
-            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-        }
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    private void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
+    protected void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
         final int numLru = lruList.size();
         if (mConstants.USE_TIERED_CACHED_ADJ) {
             final long now = SystemClock.uptimeMillis();
@@ -1413,7 +1441,7 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    private void updateAppUidRecIfNecessaryLSP(final ProcessRecord app) {
+    protected void updateAppUidRecIfNecessaryLSP(final ProcessRecord app) {
         if (!app.isKilledByAm() && app.getThread() != null) {
             if (app.isolated && app.mServices.numberOfRunningServices() <= 0
                     && app.getIsolatedEntryPoint() == null) {
@@ -1442,7 +1470,7 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    private void updateUidsLSP(ActiveUids activeUids, final long nowElapsed) {
+    protected void updateUidsLSP(ActiveUids activeUids, final long nowElapsed) {
         // This compares previously set procstate to the current procstate in regards to whether
         // or not the app's network access will be blocked. So, this needs to be called before
         // we update the UidRecord's procstate by calling {@link UidRecord#setSetProcState}.
@@ -1580,7 +1608,7 @@
         return true;
     }
 
-    private final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback =
+    protected final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback =
             new ComputeOomAdjWindowCallback();
 
     /** These methods are called inline during computeOomAdjLSP(), on the same thread */
@@ -1719,24 +1747,30 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    private boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj,
+    protected boolean computeOomAdjLSP(ProcessRecord app, int cachedAdj,
             ProcessRecord topApp, boolean doingAll, long now, boolean cycleReEval,
-            boolean computeClients) {
+            boolean computeClients, int oomAdjReason, boolean couldRecurse) {
         final ProcessStateRecord state = app.mState;
-        if (mAdjSeq == state.getAdjSeq()) {
-            if (state.getAdjSeq() == state.getCompletedAdjSeq()) {
-                // This adjustment has already been computed successfully.
-                return false;
-            } else {
-                // The process is being computed, so there is a cycle. We cannot
-                // rely on this process's state.
-                state.setContainsCycle(true);
-                mProcessesInCycle.add(app);
+        if (couldRecurse) {
+            if (mAdjSeq == state.getAdjSeq()) {
+                if (state.getAdjSeq() == state.getCompletedAdjSeq()) {
+                    // This adjustment has already been computed successfully.
+                    return false;
+                } else {
+                    // The process is being computed, so there is a cycle. We cannot
+                    // rely on this process's state.
+                    state.setContainsCycle(true);
+                    mProcessesInCycle.add(app);
 
-                return false;
+                    return false;
+                }
             }
         }
 
+        int prevAppAdj = getInitialAdj(app);
+        int prevProcState = getInitialProcState(app);
+        int prevCapability = getInitialCapability(app);
+
         if (app.getThread() == null) {
             state.setAdjSeq(mAdjSeq);
             state.setCurrentSchedulingGroup(SCHED_GROUP_BACKGROUND);
@@ -1745,6 +1779,8 @@
             state.setCurRawAdj(CACHED_APP_MAX_ADJ);
             state.setCompletedAdjSeq(state.getAdjSeq());
             state.setCurCapability(PROCESS_CAPABILITY_NONE);
+            onProcessStateChanged(app, prevProcState);
+            onProcessOomAdjChanged(app, prevAppAdj);
             return false;
         }
 
@@ -1753,7 +1789,7 @@
         state.setAdjTarget(null);
         state.setEmpty(false);
         state.setCached(false);
-        if (!cycleReEval) {
+        if (!couldRecurse || !cycleReEval) {
             // Don't reset this flag when doing cycles re-evaluation.
             state.setNoKillOnBgRestrictedAndIdle(false);
             // If this UID is currently allowlisted, it should not be frozen.
@@ -1764,9 +1800,6 @@
         final int appUid = app.info.uid;
         final int logUid = mService.mCurOomAdjUid;
 
-        int prevAppAdj = state.getCurAdj();
-        int prevProcState = state.getCurProcState();
-        int prevCapability = state.getCurCapability();
         final ProcessServiceRecord psr = app.mServices;
 
         if (state.getMaxAdj() <= FOREGROUND_APP_ADJ) {
@@ -1812,6 +1845,8 @@
             state.setCurRawProcState(state.getCurProcState());
             state.setCurAdj(state.getMaxAdj());
             state.setCompletedAdjSeq(state.getAdjSeq());
+            onProcessStateChanged(app, prevProcState);
+            onProcessOomAdjChanged(app, prevAppAdj);
             // if curAdj is less than prevAppAdj, then this process was promoted
             return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState;
         }
@@ -1825,7 +1860,7 @@
         int adj;
         int schedGroup;
         int procState;
-        int capability = cycleReEval ? app.mState.getCurCapability() : 0;
+        int capability = cycleReEval ? getInitialCapability(app) : 0;
 
         boolean foregroundActivities = false;
         boolean hasVisibleActivities = false;
@@ -1904,7 +1939,7 @@
             // value that the caller wants us to.
             adj = cachedAdj;
             procState = PROCESS_STATE_CACHED_EMPTY;
-            if (!state.containsCycle()) {
+            if (!couldRecurse || !state.containsCycle()) {
                 state.setCached(true);
                 state.setEmpty(true);
                 state.setAdjType("cch-empty");
@@ -2169,8 +2204,10 @@
             }
         }
 
-        boolean boundByNonBgRestricted = state.isCurBoundByNonBgRestrictedApp();
-        boolean scheduleLikeTopApp = false;
+        state.setCurBoundByNonBgRestrictedApp(getInitialIsCurBoundByNonBgRestrictedApp(app));
+
+        state.setScheduleLikeTopApp(false);
+
         for (int is = psr.numberOfRunningServices() - 1;
                 is >= 0 && (adj > FOREGROUND_APP_ADJ
                         || schedGroup == SCHED_GROUP_BACKGROUND
@@ -2243,6 +2280,18 @@
                 }
             }
 
+            if (!couldRecurse) {
+                // We're entering recursive functions below, if we're told it's not a recursive
+                // loop, abort here.
+                continue;
+            }
+
+
+            state.setCurRawAdj(adj);
+            state.setCurRawProcState(procState);
+            state.setCurrentSchedulingGroup(schedGroup);
+            state.setCurCapability(capability);
+
             ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections = s.getConnections();
             for (int conni = serviceConnections.size() - 1;
                     conni >= 0 && (adj > FOREGROUND_APP_ADJ
@@ -2263,335 +2312,13 @@
                         continue;
                     }
 
-                    boolean trackedProcState = false;
+                    computeServiceHostOomAdjLSP(cr, app, cr.binding.client, now, topApp, doingAll,
+                            cycleReEval, computeClients, oomAdjReason, cachedAdj, true);
 
-                    ProcessRecord client = cr.binding.client;
-                    if (app.isSdkSandbox && cr.binding.attributedClient != null) {
-                        // For SDK sandboxes, use the attributed client (eg the app that
-                        // requested the sandbox)
-                        client = cr.binding.attributedClient;
-                    }
-                    final ProcessStateRecord cstate = client.mState;
-                    if (computeClients) {
-                        computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now,
-                                cycleReEval, true);
-                    } else {
-                        cstate.setCurRawAdj(cstate.getCurAdj());
-                        cstate.setCurRawProcState(cstate.getCurProcState());
-                    }
-
-                    int clientAdj = cstate.getCurRawAdj();
-                    int clientProcState = cstate.getCurRawProcState();
-
-                    final boolean clientIsSystem = clientProcState < PROCESS_STATE_TOP;
-
-                    boundByNonBgRestricted |= cstate.isCurBoundByNonBgRestrictedApp()
-                            || clientProcState <= PROCESS_STATE_BOUND_TOP
-                            || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
-                                    && !cstate.isBackgroundRestricted());
-
-                    if (client.mOptRecord.shouldNotFreeze()) {
-                        // Propagate the shouldNotFreeze flag down the bindings.
-                        app.mOptRecord.setShouldNotFreeze(true);
-                    }
-
-                    // We always propagate PROCESS_CAPABILITY_BFSL over bindings here,
-                    // but, right before actually setting it to the process,
-                    // we check the final procstate, and remove it if the procsate is below BFGS.
-                    capability |= getBfslCapabilityFromClient(client);
-
-                    if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) {
-                        if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
-                            capability |= cstate.getCurCapability();
-                        }
-
-                        // If an app has network capability by default
-                        // (by having procstate <= BFGS), then the apps it binds to will get
-                        // elevated to a high enough procstate anyway to get network unless they
-                        // request otherwise, so don't propagate the network capability by default
-                        // in this case unless they explicitly request it.
-                        if ((cstate.getCurCapability()
-                                & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0) {
-                            if (clientProcState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
-                                // This is used to grant network access to Expedited Jobs.
-                                if (cr.hasFlag(Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS)) {
-                                    capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
-                                }
-                            } else {
-                                capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
-                            }
-                        }
-                        if ((cstate.getCurCapability()
-                                & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0) {
-                            if (clientProcState <= PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                                // This is used to grant network access to User Initiated Jobs.
-                                if (cr.hasFlag(Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) {
-                                    capability |= PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
-                                }
-                            }
-                        }
-
-                        if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
-                            continue;
-                        }
-
-                        if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
-                            // If the other app is cached for any reason, for purposes here
-                            // we are going to consider it empty.  The specific cached state
-                            // doesn't propagate except under certain conditions.
-                            clientProcState = PROCESS_STATE_CACHED_EMPTY;
-                        }
-                        String adjType = null;
-                        if (cr.hasFlag(Context.BIND_ALLOW_OOM_MANAGEMENT)) {
-                            // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen.
-                            if (clientAdj < CACHED_APP_MIN_ADJ) {
-                                app.mOptRecord.setShouldNotFreeze(true);
-                            }
-                            // Not doing bind OOM management, so treat
-                            // this guy more like a started service.
-                            if (state.hasShownUi() && !state.getCachedIsHomeProcess()) {
-                                // If this process has shown some UI, let it immediately
-                                // go to the LRU list because it may be pretty heavy with
-                                // UI stuff.  We'll tag it with a label just to help
-                                // debug and understand what is going on.
-                                if (adj > clientAdj) {
-                                    adjType = "cch-bound-ui-services";
-                                }
-                                state.setCached(false);
-                                clientAdj = adj;
-                                clientProcState = procState;
-                            } else {
-                                if (now >= (s.lastActivity
-                                        + mConstants.MAX_SERVICE_INACTIVITY)) {
-                                    // This service has not seen activity within
-                                    // recent memory, so allow it to drop to the
-                                    // LRU list if there is no other reason to keep
-                                    // it around.  We'll also tag it with a label just
-                                    // to help debug and undertand what is going on.
-                                    if (adj > clientAdj) {
-                                        adjType = "cch-bound-services";
-                                    }
-                                    clientAdj = adj;
-                                }
-                            }
-                        }
-                        if (adj > clientAdj) {
-                            // If this process has recently shown UI, and
-                            // the process that is binding to it is less
-                            // important than being visible, then we don't
-                            // care about the binding as much as we care
-                            // about letting this process get into the LRU
-                            // list to be killed and restarted if needed for
-                            // memory.
-                            if (state.hasShownUi() && !state.getCachedIsHomeProcess()
-                                    && clientAdj > PERCEPTIBLE_APP_ADJ) {
-                                if (adj >= CACHED_APP_MIN_ADJ) {
-                                    adjType = "cch-bound-ui-services";
-                                }
-                            } else {
-                                int newAdj;
-                                int lbAdj = VISIBLE_APP_ADJ; // lower bound of adj.
-                                if (cr.hasFlag(Context.BIND_ABOVE_CLIENT
-                                        | Context.BIND_IMPORTANT)) {
-                                    if (clientAdj >= PERSISTENT_SERVICE_ADJ) {
-                                        newAdj = clientAdj;
-                                    } else {
-                                        // make this service persistent
-                                        newAdj = PERSISTENT_SERVICE_ADJ;
-                                        schedGroup = SCHED_GROUP_DEFAULT;
-                                        procState = ActivityManager.PROCESS_STATE_PERSISTENT;
-                                        cr.trackProcState(procState, mAdjSeq);
-                                        trackedProcState = true;
-                                    }
-                                } else if (cr.hasFlag(Context.BIND_NOT_PERCEPTIBLE)
-                                        && clientAdj <= PERCEPTIBLE_APP_ADJ
-                                        && adj >= (lbAdj = PERCEPTIBLE_LOW_APP_ADJ)) {
-                                    newAdj = PERCEPTIBLE_LOW_APP_ADJ;
-                                } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE)
-                                        && cr.notHasFlag(Context.BIND_NOT_FOREGROUND)
-                                        && clientAdj < PERCEPTIBLE_APP_ADJ
-                                        && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) {
-                                    // This is for user-initiated jobs.
-                                    // We use APP_ADJ + 1 here, so we can tell them apart from FGS.
-                                    newAdj = PERCEPTIBLE_APP_ADJ + 1;
-                                } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE)
-                                        && cr.hasFlag(Context.BIND_NOT_FOREGROUND)
-                                        && clientAdj < PERCEPTIBLE_APP_ADJ
-                                        && adj >= (lbAdj = (PERCEPTIBLE_MEDIUM_APP_ADJ + 2))) {
-                                    // This is for expedited jobs.
-                                    // We use MEDIUM_APP_ADJ + 2 here, so we can tell apart
-                                    // EJ and short-FGS.
-                                    newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 2;
-                                } else if (cr.hasFlag(Context.BIND_NOT_VISIBLE)
-                                        && clientAdj < PERCEPTIBLE_APP_ADJ
-                                        && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) {
-                                    newAdj = PERCEPTIBLE_APP_ADJ;
-                                } else if (clientAdj >= PERCEPTIBLE_APP_ADJ) {
-                                    newAdj = clientAdj;
-                                } else if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)
-                                        && clientAdj <= VISIBLE_APP_ADJ
-                                        && adj > VISIBLE_APP_ADJ) {
-                                    newAdj = VISIBLE_APP_ADJ;
-                                } else {
-                                    if (adj > VISIBLE_APP_ADJ) {
-                                        // TODO: Is this too limiting for apps bound from TOP?
-                                        newAdj = Math.max(clientAdj, lbAdj);
-                                    } else {
-                                        newAdj = adj;
-                                    }
-                                }
-                                if (!cstate.isCached()) {
-                                    state.setCached(false);
-                                }
-                                if (adj >  newAdj) {
-                                    adj = newAdj;
-                                    state.setCurRawAdj(adj);
-                                    adjType = "service";
-                                }
-                            }
-                        }
-                        if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND
-                                | Context.BIND_IMPORTANT_BACKGROUND)) {
-                            // This will treat important bound services identically to
-                            // the top app, which may behave differently than generic
-                            // foreground work.
-                            final int curSchedGroup = cstate.getCurrentSchedulingGroup();
-                            if (curSchedGroup > schedGroup) {
-                                if (cr.hasFlag(Context.BIND_IMPORTANT)) {
-                                    schedGroup = curSchedGroup;
-                                } else {
-                                    schedGroup = SCHED_GROUP_DEFAULT;
-                                }
-                            }
-                            if (clientProcState < PROCESS_STATE_TOP) {
-                                // Special handling for above-top states (persistent
-                                // processes).  These should not bring the current process
-                                // into the top state, since they are not on top.  Instead
-                                // give them the best bound state after that.
-                                if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)) {
-                                    clientProcState = PROCESS_STATE_FOREGROUND_SERVICE;
-                                } else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) {
-                                    clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-                                } else if (mService.mWakefulness.get()
-                                        == PowerManagerInternal.WAKEFULNESS_AWAKE
-                                        && cr.hasFlag(Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE))
-                                {
-                                    clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-                                } else {
-                                    clientProcState =
-                                            PROCESS_STATE_IMPORTANT_FOREGROUND;
-                                }
-                            } else if (clientProcState == PROCESS_STATE_TOP) {
-                                // Go at most to BOUND_TOP, unless requested to elevate
-                                // to client's state.
-                                clientProcState = PROCESS_STATE_BOUND_TOP;
-                                final boolean enabled = cstate.getCachedCompatChange(
-                                        CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY);
-                                if (enabled) {
-                                    if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
-                                        // TOP process passes all capabilities to the service.
-                                        capability |= cstate.getCurCapability();
-                                    } else {
-                                        // TOP process passes no capability to the service.
-                                    }
-                                } else {
-                                    // TOP process passes all capabilities to the service.
-                                    capability |= cstate.getCurCapability();
-                                }
-                            }
-                        } else if (cr.notHasFlag(Context.BIND_IMPORTANT_BACKGROUND)) {
-                            if (clientProcState <
-                                    PROCESS_STATE_TRANSIENT_BACKGROUND) {
-                                clientProcState =
-                                        PROCESS_STATE_TRANSIENT_BACKGROUND;
-                            }
-                        } else {
-                            if (clientProcState <
-                                    PROCESS_STATE_IMPORTANT_BACKGROUND) {
-                                clientProcState =
-                                        PROCESS_STATE_IMPORTANT_BACKGROUND;
-                            }
-                        }
-
-                        if (schedGroup < SCHED_GROUP_TOP_APP
-                                && cr.hasFlag(Context.BIND_SCHEDULE_LIKE_TOP_APP)
-                                && clientIsSystem) {
-                            schedGroup = SCHED_GROUP_TOP_APP;
-                            scheduleLikeTopApp = true;
-                        }
-
-                        if (!trackedProcState) {
-                            cr.trackProcState(clientProcState, mAdjSeq);
-                        }
-
-                        if (procState > clientProcState) {
-                            procState = clientProcState;
-                            state.setCurRawProcState(procState);
-                            if (adjType == null) {
-                                adjType = "service";
-                            }
-                        }
-                        if (procState < PROCESS_STATE_IMPORTANT_BACKGROUND
-                                && cr.hasFlag(Context.BIND_SHOWING_UI)) {
-                            app.setPendingUiClean(true);
-                        }
-                        if (adjType != null) {
-                            state.setAdjType(adjType);
-                            state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
-                                    .REASON_SERVICE_IN_USE);
-                            state.setAdjSource(client);
-                            state.setAdjSourceProcState(clientProcState);
-                            state.setAdjTarget(s.instanceName);
-                            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
-                                        + ": " + app + ", due to " + client
-                                        + " adj=" + adj + " procState="
-                                        + ProcessList.makeProcStateString(procState));
-                            }
-                        }
-                    } else { // BIND_WAIVE_PRIORITY == true
-                        // BIND_WAIVE_PRIORITY bindings are special when it comes to the
-                        // freezer. Processes bound via WPRI are expected to be running,
-                        // but they are not promoted in the LRU list to keep them out of
-                        // cached. As a result, they can freeze based on oom_adj alone.
-                        // Normally, bindToDeath would fire when a cached app would die
-                        // in the background, but nothing will fire when a running process
-                        // pings a frozen process. Accordingly, any cached app that is
-                        // bound by an unfrozen app via a WPRI binding has to remain
-                        // unfrozen.
-                        if (clientAdj < CACHED_APP_MIN_ADJ) {
-                            app.mOptRecord.setShouldNotFreeze(true);
-                        }
-                    }
-                    if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) {
-                        psr.setTreatLikeActivity(true);
-                    }
-                    final ActivityServiceConnectionsHolder a = cr.activity;
-                    if (cr.hasFlag(Context.BIND_ADJUST_WITH_ACTIVITY)) {
-                        if (a != null && adj > FOREGROUND_APP_ADJ
-                                && a.isActivityVisible()) {
-                            adj = FOREGROUND_APP_ADJ;
-                            state.setCurRawAdj(adj);
-                            if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND)) {
-                                if (cr.hasFlag(Context.BIND_IMPORTANT)) {
-                                    schedGroup = SCHED_GROUP_TOP_APP_BOUND;
-                                } else {
-                                    schedGroup = SCHED_GROUP_DEFAULT;
-                                }
-                            }
-                            state.setCached(false);
-                            state.setAdjType("service");
-                            state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
-                                    .REASON_SERVICE_IN_USE);
-                            state.setAdjSource(a);
-                            state.setAdjSourceProcState(procState);
-                            state.setAdjTarget(s.instanceName);
-                            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                                reportOomAdjMessageLocked(TAG_OOM_ADJ,
-                                        "Raise to service w/activity: " + app);
-                            }
-                        }
-                    }
+                    adj = state.getCurRawAdj();
+                    procState = state.getCurRawProcState();
+                    schedGroup = state.getCurrentSchedulingGroup();
+                    capability = state.getCurCapability();
                 }
             }
         }
@@ -2603,97 +2330,27 @@
                         || procState > PROCESS_STATE_TOP);
                 provi--) {
             ContentProviderRecord cpr = ppr.getProviderAt(provi);
-            for (int i = cpr.connections.size() - 1;
-                    i >= 0 && (adj > FOREGROUND_APP_ADJ
-                            || schedGroup == SCHED_GROUP_BACKGROUND
-                            || procState > PROCESS_STATE_TOP);
-                    i--) {
-                ContentProviderConnection conn = cpr.connections.get(i);
-                ProcessRecord client = conn.client;
-                final ProcessStateRecord cstate = client.mState;
-                if (client == app) {
-                    // Being our own client is not interesting.
-                    continue;
-                }
-                if (computeClients) {
-                    computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true);
-                } else {
-                    cstate.setCurRawAdj(cstate.getCurAdj());
-                    cstate.setCurRawProcState(cstate.getCurProcState());
-                }
+            if (couldRecurse) {
+                // We're entering recursive functions below.
+                state.setCurRawAdj(adj);
+                state.setCurRawProcState(procState);
+                state.setCurrentSchedulingGroup(schedGroup);
+                state.setCurCapability(capability);
 
-                if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
-                    continue;
-                }
+                for (int i = cpr.connections.size() - 1;
+                        i >= 0 && (adj > FOREGROUND_APP_ADJ
+                                || schedGroup == SCHED_GROUP_BACKGROUND
+                                || procState > PROCESS_STATE_TOP);
+                        i--) {
+                    ContentProviderConnection conn = cpr.connections.get(i);
+                    ProcessRecord client = conn.client;
+                    computeProviderHostOomAdjLSP(conn, app, client, now, topApp, doingAll,
+                            cycleReEval, computeClients, oomAdjReason, cachedAdj, true);
 
-                int clientAdj = cstate.getCurRawAdj();
-                int clientProcState = cstate.getCurRawProcState();
-
-                // We always propagate PROCESS_CAPABILITY_BFSL to providers here,
-                // but, right before actually setting it to the process,
-                // we check the final procstate, and remove it if the procsate is below BFGS.
-                capability |= getBfslCapabilityFromClient(client);
-
-                if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
-                    // If the other app is cached for any reason, for purposes here
-                    // we are going to consider it empty.
-                    clientProcState = PROCESS_STATE_CACHED_EMPTY;
-                }
-                if (client.mOptRecord.shouldNotFreeze()) {
-                    // Propagate the shouldNotFreeze flag down the bindings.
-                    app.mOptRecord.setShouldNotFreeze(true);
-                }
-
-                boundByNonBgRestricted |= cstate.isCurBoundByNonBgRestrictedApp()
-                        || clientProcState <= PROCESS_STATE_BOUND_TOP
-                        || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
-                                && !cstate.isBackgroundRestricted());
-
-                String adjType = null;
-                if (adj > clientAdj) {
-                    if (state.hasShownUi() && !state.getCachedIsHomeProcess()
-                            && clientAdj > PERCEPTIBLE_APP_ADJ) {
-                        adjType = "cch-ui-provider";
-                    } else {
-                        adj = Math.max(clientAdj, FOREGROUND_APP_ADJ);
-                        state.setCurRawAdj(adj);
-                        adjType = "provider";
-                    }
-                    state.setCached(state.isCached() & cstate.isCached());
-                }
-
-                if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) {
-                    if (adjType == null) {
-                        adjType = "provider";
-                    }
-                    if (clientProcState == PROCESS_STATE_TOP) {
-                        clientProcState = PROCESS_STATE_BOUND_TOP;
-                    } else {
-                        clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-                    }
-                }
-
-                conn.trackProcState(clientProcState, mAdjSeq);
-                if (procState > clientProcState) {
-                    procState = clientProcState;
-                    state.setCurRawProcState(procState);
-                }
-                if (cstate.getCurrentSchedulingGroup() > schedGroup) {
-                    schedGroup = SCHED_GROUP_DEFAULT;
-                }
-                if (adjType != null) {
-                    state.setAdjType(adjType);
-                    state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
-                            .REASON_PROVIDER_IN_USE);
-                    state.setAdjSource(client);
-                    state.setAdjSourceProcState(clientProcState);
-                    state.setAdjTarget(cpr.name);
-                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                        reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
-                                + ": " + app + ", due to " + client
-                                + " adj=" + adj + " procState="
-                                + ProcessList.makeProcStateString(procState));
-                    }
+                    adj = state.getCurRawAdj();
+                    procState = state.getCurRawProcState();
+                    schedGroup = state.getCurrentSchedulingGroup();
+                    capability = state.getCurCapability();
                 }
             }
             // If the provider has external (non-framework) process
@@ -2799,7 +2456,7 @@
         // restrictions on screen off
         if (procState >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE
                 && mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
-                && !scheduleLikeTopApp) {
+                && !state.shouldScheduleLikeTopApp()) {
             if (schedGroup > SCHED_GROUP_RESTRICTED) {
                 schedGroup = SCHED_GROUP_RESTRICTED;
             }
@@ -2817,6 +2474,7 @@
             capability &= ~PROCESS_CAPABILITY_BFSL;
         }
 
+        state.setHasForegroundActivities(foregroundActivities);
 
         if (app.isPendingFinishAttach()) {
             // If the app is still starting up. We reset the computations to the
@@ -2834,22 +2492,580 @@
         // it when computing the final cached adj later.  Note that we don't need to
         // worry about this for max adj above, since max adj will always be used to
         // keep it out of the cached vaues.
-        state.setCurAdj(adj);
         state.setCurCapability(capability);
-        state.setCurrentSchedulingGroup(schedGroup);
-        state.setCurProcState(procState);
-        state.setCurRawProcState(procState);
         state.updateLastInvisibleTime(hasVisibleActivities);
-        state.setHasForegroundActivities(foregroundActivities);
         state.setCompletedAdjSeq(mAdjSeq);
-        state.setCurBoundByNonBgRestrictedApp(boundByNonBgRestricted);
+
+        schedGroup = setIntermediateAdjLSP(app, adj, prevAppAdj, schedGroup);
+        setIntermediateProcStateLSP(app, procState, prevProcState);
+        setIntermediateSchedGroupLSP(state, schedGroup);
 
         // if curAdj or curProcState improved, then this process was promoted
         return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState
                 || state.getCurCapability() != prevCapability;
     }
 
-    private int getDefaultCapability(ProcessRecord app, int procState) {
+    /**
+     * @return The proposed change to the schedGroup.
+     */
+    @GuardedBy({"mService", "mProcLock"})
+    protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj,
+            int schedGroup) {
+        final ProcessStateRecord state = app.mState;
+        state.setCurRawAdj(adj);
+
+        adj = app.mServices.modifyRawOomAdj(adj);
+        if (adj > state.getMaxAdj()) {
+            adj = state.getMaxAdj();
+            if (adj <= PERCEPTIBLE_LOW_APP_ADJ) {
+                schedGroup = SCHED_GROUP_DEFAULT;
+            }
+        }
+
+        state.setCurAdj(adj);
+
+        return schedGroup;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    protected void setIntermediateProcStateLSP(ProcessRecord app, int procState,
+            int prevProcState) {
+        final ProcessStateRecord state = app.mState;
+        state.setCurProcState(procState);
+        state.setCurRawProcState(procState);
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    protected void setIntermediateSchedGroupLSP(ProcessStateRecord state, int schedGroup) {
+        // Put bound foreground services in a special sched group for additional
+        // restrictions on screen off
+        if (state.getCurProcState() >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+                && mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE
+                && !state.shouldScheduleLikeTopApp()) {
+            if (schedGroup > SCHED_GROUP_RESTRICTED) {
+                schedGroup = SCHED_GROUP_RESTRICTED;
+            }
+        }
+
+        state.setCurrentSchedulingGroup(schedGroup);
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    protected void computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app,
+            ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
+            boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj,
+            boolean couldRecurse) {
+        if (app.isPendingFinishAttach()) {
+            // We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here.
+            return;
+        }
+
+        final ProcessStateRecord state = app.mState;
+        ProcessStateRecord cstate = client.mState;
+
+        if (couldRecurse) {
+            if (app.isSdkSandbox && cr.binding.attributedClient != null) {
+                // For SDK sandboxes, use the attributed client (eg the app that
+                // requested the sandbox)
+                client = cr.binding.attributedClient;
+                cstate = client.mState;
+            }
+            if (computeClients) {
+                computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true,
+                        oomAdjReason, true);
+            } else {
+                cstate.setCurRawAdj(cstate.getCurAdj());
+                cstate.setCurRawProcState(cstate.getCurProcState());
+            }
+        }
+
+        int clientAdj = cstate.getCurRawAdj();
+        int clientProcState = cstate.getCurRawProcState();
+
+        final boolean clientIsSystem = clientProcState < PROCESS_STATE_TOP;
+
+        int adj = state.getCurRawAdj();
+        int procState = state.getCurRawProcState();
+        int schedGroup = state.getCurrentSchedulingGroup();
+        int capability = state.getCurCapability();
+
+        final int prevRawAdj = adj;
+        final int prevProcState = procState;
+        final int prevSchedGroup = schedGroup;
+
+        final int appUid = app.info.uid;
+        final int logUid = mService.mCurOomAdjUid;
+
+        state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp()
+                || cstate.isCurBoundByNonBgRestrictedApp()
+                || clientProcState <= PROCESS_STATE_BOUND_TOP
+                || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
+                        && !cstate.isBackgroundRestricted()));
+
+        if (client.mOptRecord.shouldNotFreeze()) {
+            // Propagate the shouldNotFreeze flag down the bindings.
+            app.mOptRecord.setShouldNotFreeze(true);
+        }
+
+        boolean trackedProcState = false;
+
+        // We always propagate PROCESS_CAPABILITY_BFSL over bindings here,
+        // but, right before actually setting it to the process,
+        // we check the final procstate, and remove it if the procsate is below BFGS.
+        capability |= getBfslCapabilityFromClient(client);
+
+        if (cr.notHasFlag(Context.BIND_WAIVE_PRIORITY)) {
+            if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
+                capability |= cstate.getCurCapability();
+            }
+
+            // If an app has network capability by default
+            // (by having procstate <= BFGS), then the apps it binds to will get
+            // elevated to a high enough procstate anyway to get network unless they
+            // request otherwise, so don't propagate the network capability by default
+            // in this case unless they explicitly request it.
+            if ((cstate.getCurCapability()
+                    & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0) {
+                if (clientProcState <= PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+                    // This is used to grant network access to Expedited Jobs.
+                    if (cr.hasFlag(Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS)) {
+                        capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
+                    }
+                } else {
+                    capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
+                }
+            }
+            if ((cstate.getCurCapability()
+                    & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0) {
+                if (clientProcState <= PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                    // This is used to grant network access to User Initiated Jobs.
+                    if (cr.hasFlag(Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) {
+                        capability |= PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
+                    }
+                }
+            }
+
+            if (couldRecurse && shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
+                return;
+            }
+
+            if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
+                // If the other app is cached for any reason, for purposes here
+                // we are going to consider it empty.  The specific cached state
+                // doesn't propagate except under certain conditions.
+                clientProcState = PROCESS_STATE_CACHED_EMPTY;
+            }
+            String adjType = null;
+            if (cr.hasFlag(Context.BIND_ALLOW_OOM_MANAGEMENT)) {
+                // Similar to BIND_WAIVE_PRIORITY, keep it unfrozen.
+                if (clientAdj < CACHED_APP_MIN_ADJ) {
+                    app.mOptRecord.setShouldNotFreeze(true);
+                }
+                // Not doing bind OOM management, so treat
+                // this guy more like a started service.
+                if (state.hasShownUi() && !state.getCachedIsHomeProcess()) {
+                    // If this process has shown some UI, let it immediately
+                    // go to the LRU list because it may be pretty heavy with
+                    // UI stuff.  We'll tag it with a label just to help
+                    // debug and understand what is going on.
+                    if (adj > clientAdj) {
+                        adjType = "cch-bound-ui-services";
+                    }
+                    state.setCached(false);
+                    clientAdj = adj;
+                    clientProcState = procState;
+                } else {
+                    if (now >= (cr.binding.service.lastActivity
+                            + mConstants.MAX_SERVICE_INACTIVITY)) {
+                        // This service has not seen activity within
+                        // recent memory, so allow it to drop to the
+                        // LRU list if there is no other reason to keep
+                        // it around.  We'll also tag it with a label just
+                        // to help debug and undertand what is going on.
+                        if (adj > clientAdj) {
+                            adjType = "cch-bound-services";
+                        }
+                        clientAdj = adj;
+                    }
+                }
+            }
+            if (adj > clientAdj) {
+                // If this process has recently shown UI, and
+                // the process that is binding to it is less
+                // important than being visible, then we don't
+                // care about the binding as much as we care
+                // about letting this process get into the LRU
+                // list to be killed and restarted if needed for
+                // memory.
+                if (state.hasShownUi() && !state.getCachedIsHomeProcess()
+                        && clientAdj > PERCEPTIBLE_APP_ADJ) {
+                    if (adj >= CACHED_APP_MIN_ADJ) {
+                        adjType = "cch-bound-ui-services";
+                    }
+                } else {
+                    int newAdj;
+                    int lbAdj = VISIBLE_APP_ADJ; // lower bound of adj.
+                    if (cr.hasFlag(Context.BIND_ABOVE_CLIENT
+                            | Context.BIND_IMPORTANT)) {
+                        if (clientAdj >= PERSISTENT_SERVICE_ADJ) {
+                            newAdj = clientAdj;
+                        } else {
+                            // make this service persistent
+                            newAdj = PERSISTENT_SERVICE_ADJ;
+                            schedGroup = SCHED_GROUP_DEFAULT;
+                            procState = ActivityManager.PROCESS_STATE_PERSISTENT;
+                            cr.trackProcState(procState, mAdjSeq);
+                            trackedProcState = true;
+                        }
+                    } else if (cr.hasFlag(Context.BIND_NOT_PERCEPTIBLE)
+                            && clientAdj <= PERCEPTIBLE_APP_ADJ
+                            && adj >= (lbAdj = PERCEPTIBLE_LOW_APP_ADJ)) {
+                        newAdj = PERCEPTIBLE_LOW_APP_ADJ;
+                    } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE)
+                            && cr.notHasFlag(Context.BIND_NOT_FOREGROUND)
+                            && clientAdj < PERCEPTIBLE_APP_ADJ
+                            && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) {
+                        // This is for user-initiated jobs.
+                        // We use APP_ADJ + 1 here, so we can tell them apart from FGS.
+                        newAdj = PERCEPTIBLE_APP_ADJ + 1;
+                    } else if (cr.hasFlag(Context.BIND_ALMOST_PERCEPTIBLE)
+                            && cr.hasFlag(Context.BIND_NOT_FOREGROUND)
+                            && clientAdj < PERCEPTIBLE_APP_ADJ
+                            && adj >= (lbAdj = (PERCEPTIBLE_MEDIUM_APP_ADJ + 2))) {
+                        // This is for expedited jobs.
+                        // We use MEDIUM_APP_ADJ + 2 here, so we can tell apart
+                        // EJ and short-FGS.
+                        newAdj = PERCEPTIBLE_MEDIUM_APP_ADJ + 2;
+                    } else if (cr.hasFlag(Context.BIND_NOT_VISIBLE)
+                            && clientAdj < PERCEPTIBLE_APP_ADJ
+                            && adj >= (lbAdj = PERCEPTIBLE_APP_ADJ)) {
+                        newAdj = PERCEPTIBLE_APP_ADJ;
+                    } else if (clientAdj >= PERCEPTIBLE_APP_ADJ) {
+                        newAdj = clientAdj;
+                    } else if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)
+                            && clientAdj <= VISIBLE_APP_ADJ
+                            && adj > VISIBLE_APP_ADJ) {
+                        newAdj = VISIBLE_APP_ADJ;
+                    } else {
+                        if (adj > VISIBLE_APP_ADJ) {
+                            // TODO: Is this too limiting for apps bound from TOP?
+                            newAdj = Math.max(clientAdj, lbAdj);
+                        } else {
+                            newAdj = adj;
+                        }
+                    }
+                    if (!cstate.isCached()) {
+                        state.setCached(false);
+                    }
+                    if (adj >  newAdj) {
+                        adj = newAdj;
+                        state.setCurRawAdj(adj);
+                        adjType = "service";
+                    }
+                }
+            }
+            if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND
+                    | Context.BIND_IMPORTANT_BACKGROUND)) {
+                // This will treat important bound services identically to
+                // the top app, which may behave differently than generic
+                // foreground work.
+                final int curSchedGroup = cstate.getCurrentSchedulingGroup();
+                if (curSchedGroup > schedGroup) {
+                    if (cr.hasFlag(Context.BIND_IMPORTANT)) {
+                        schedGroup = curSchedGroup;
+                    } else {
+                        schedGroup = SCHED_GROUP_DEFAULT;
+                    }
+                }
+                if (clientProcState < PROCESS_STATE_TOP) {
+                    // Special handling for above-top states (persistent
+                    // processes).  These should not bring the current process
+                    // into the top state, since they are not on top.  Instead
+                    // give them the best bound state after that.
+                    if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)) {
+                        clientProcState = PROCESS_STATE_FOREGROUND_SERVICE;
+                    } else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) {
+                        clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+                    } else if (mService.mWakefulness.get()
+                            == PowerManagerInternal.WAKEFULNESS_AWAKE
+                            && cr.hasFlag(Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)) {
+                        clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+                    } else {
+                        clientProcState =
+                                PROCESS_STATE_IMPORTANT_FOREGROUND;
+                    }
+                } else if (clientProcState == PROCESS_STATE_TOP) {
+                    // Go at most to BOUND_TOP, unless requested to elevate
+                    // to client's state.
+                    clientProcState = PROCESS_STATE_BOUND_TOP;
+                    final boolean enabled = cstate.getCachedCompatChange(
+                            CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY);
+                    if (enabled) {
+                        if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
+                            // TOP process passes all capabilities to the service.
+                            capability |= cstate.getCurCapability();
+                        } else {
+                            // TOP process passes no capability to the service.
+                        }
+                    } else {
+                        // TOP process passes all capabilities to the service.
+                        capability |= cstate.getCurCapability();
+                    }
+                }
+            } else if (cr.notHasFlag(Context.BIND_IMPORTANT_BACKGROUND)) {
+                if (clientProcState < PROCESS_STATE_TRANSIENT_BACKGROUND) {
+                    clientProcState =
+                            PROCESS_STATE_TRANSIENT_BACKGROUND;
+                }
+            } else {
+                if (clientProcState < PROCESS_STATE_IMPORTANT_BACKGROUND) {
+                    clientProcState =
+                            PROCESS_STATE_IMPORTANT_BACKGROUND;
+                }
+            }
+
+            if (schedGroup < SCHED_GROUP_TOP_APP
+                    && cr.hasFlag(Context.BIND_SCHEDULE_LIKE_TOP_APP)
+                    && clientIsSystem) {
+                schedGroup = SCHED_GROUP_TOP_APP;
+                state.setScheduleLikeTopApp(true);
+            }
+
+            if (!trackedProcState) {
+                cr.trackProcState(clientProcState, mAdjSeq);
+            }
+
+            if (procState > clientProcState) {
+                procState = clientProcState;
+                state.setCurRawProcState(procState);
+                if (adjType == null) {
+                    adjType = "service";
+                }
+            }
+            if (procState < PROCESS_STATE_IMPORTANT_BACKGROUND
+                    && cr.hasFlag(Context.BIND_SHOWING_UI)) {
+                app.setPendingUiClean(true);
+            }
+            if (adjType != null) {
+                state.setAdjType(adjType);
+                state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
+                        .REASON_SERVICE_IN_USE);
+                state.setAdjSource(client);
+                state.setAdjSourceProcState(clientProcState);
+                state.setAdjTarget(cr.binding.service.instanceName);
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
+                            + ": " + app + ", due to " + client
+                            + " adj=" + adj + " procState="
+                            + ProcessList.makeProcStateString(procState));
+                }
+            }
+        } else { // BIND_WAIVE_PRIORITY == true
+            // BIND_WAIVE_PRIORITY bindings are special when it comes to the
+            // freezer. Processes bound via WPRI are expected to be running,
+            // but they are not promoted in the LRU list to keep them out of
+            // cached. As a result, they can freeze based on oom_adj alone.
+            // Normally, bindToDeath would fire when a cached app would die
+            // in the background, but nothing will fire when a running process
+            // pings a frozen process. Accordingly, any cached app that is
+            // bound by an unfrozen app via a WPRI binding has to remain
+            // unfrozen.
+            if (clientAdj < CACHED_APP_MIN_ADJ) {
+                app.mOptRecord.setShouldNotFreeze(true);
+            }
+        }
+        if (cr.hasFlag(Context.BIND_TREAT_LIKE_ACTIVITY)) {
+            app.mServices.setTreatLikeActivity(true);
+            if (clientProcState <= PROCESS_STATE_CACHED_ACTIVITY
+                    && procState > PROCESS_STATE_CACHED_ACTIVITY) {
+                // This is a cached process, but somebody wants us to treat it like it has
+                // an activity, okay!
+                procState = PROCESS_STATE_CACHED_ACTIVITY;
+                state.setAdjType("cch-as-act");
+            }
+        }
+        final ActivityServiceConnectionsHolder a = cr.activity;
+        if (cr.hasFlag(Context.BIND_ADJUST_WITH_ACTIVITY)) {
+            if (a != null && adj > FOREGROUND_APP_ADJ
+                    && a.isActivityVisible()) {
+                adj = FOREGROUND_APP_ADJ;
+                state.setCurRawAdj(adj);
+                if (cr.notHasFlag(Context.BIND_NOT_FOREGROUND)) {
+                    if (cr.hasFlag(Context.BIND_IMPORTANT)) {
+                        schedGroup = SCHED_GROUP_TOP_APP_BOUND;
+                    } else {
+                        schedGroup = SCHED_GROUP_DEFAULT;
+                    }
+                }
+                state.setCached(false);
+                state.setAdjType("service");
+                state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
+                        .REASON_SERVICE_IN_USE);
+                state.setAdjSource(a);
+                state.setAdjSourceProcState(procState);
+                state.setAdjTarget(cr.binding.service.instanceName);
+                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ,
+                            "Raise to service w/activity: " + app);
+                }
+            }
+        }
+
+        capability |= getDefaultCapability(app, procState);
+
+        // Procstates below BFGS should never have this capability.
+        if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+            capability &= ~PROCESS_CAPABILITY_BFSL;
+        }
+
+        if (adj < prevRawAdj) {
+            schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup);
+        }
+        if (procState < prevProcState) {
+            setIntermediateProcStateLSP(app, procState, prevProcState);
+        }
+        if (schedGroup > prevSchedGroup) {
+            setIntermediateSchedGroupLSP(state, schedGroup);
+        }
+        state.setCurCapability(capability);
+
+        state.setEmpty(false);
+    }
+
+    protected void computeProviderHostOomAdjLSP(ContentProviderConnection conn, ProcessRecord app,
+            ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
+            boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj,
+            boolean couldRecurse) {
+        if (app.isPendingFinishAttach()) {
+            // We've set the attaching process state in the computeInitialOomAdjLSP. Skip it here.
+            return;
+        }
+
+        final ProcessStateRecord state = app.mState;
+        final ProcessStateRecord cstate = client.mState;
+
+        if (client == app) {
+            // Being our own client is not interesting.
+            return;
+        }
+        if (couldRecurse) {
+            if (computeClients) {
+                computeOomAdjLSP(client, cachedAdj, topApp, doingAll, now, cycleReEval, true,
+                        oomAdjReason, true);
+            } else if (couldRecurse) {
+                cstate.setCurRawAdj(cstate.getCurAdj());
+                cstate.setCurRawProcState(cstate.getCurProcState());
+            }
+
+            if (shouldSkipDueToCycle(app, cstate, state.getCurRawProcState(), state.getCurRawAdj(),
+                    cycleReEval)) {
+                return;
+            }
+        }
+
+        int clientAdj = cstate.getCurRawAdj();
+        int clientProcState = cstate.getCurRawProcState();
+
+        int adj = state.getCurRawAdj();
+        int procState = state.getCurRawProcState();
+        int schedGroup = state.getCurrentSchedulingGroup();
+        int capability = state.getCurCapability();
+
+        final int prevRawAdj = adj;
+        final int prevProcState = procState;
+        final int prevSchedGroup = schedGroup;
+
+        final int appUid = app.info.uid;
+        final int logUid = mService.mCurOomAdjUid;
+
+        // We always propagate PROCESS_CAPABILITY_BFSL to providers here,
+        // but, right before actually setting it to the process,
+        // we check the final procstate, and remove it if the procsate is below BFGS.
+        capability |= getBfslCapabilityFromClient(client);
+
+        if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
+            // If the other app is cached for any reason, for purposes here
+            // we are going to consider it empty.
+            clientProcState = PROCESS_STATE_CACHED_EMPTY;
+        }
+        if (client.mOptRecord.shouldNotFreeze()) {
+            // Propagate the shouldNotFreeze flag down the bindings.
+            app.mOptRecord.setShouldNotFreeze(true);
+        }
+
+        state.setCurBoundByNonBgRestrictedApp(state.isCurBoundByNonBgRestrictedApp()
+                || cstate.isCurBoundByNonBgRestrictedApp()
+                || clientProcState <= PROCESS_STATE_BOUND_TOP
+                || (clientProcState == PROCESS_STATE_FOREGROUND_SERVICE
+                        && !cstate.isBackgroundRestricted()));
+
+        String adjType = null;
+        if (adj > clientAdj) {
+            if (state.hasShownUi() && !state.getCachedIsHomeProcess()
+                    && clientAdj > PERCEPTIBLE_APP_ADJ) {
+                adjType = "cch-ui-provider";
+            } else {
+                adj = Math.max(clientAdj, FOREGROUND_APP_ADJ);
+                state.setCurRawAdj(adj);
+                adjType = "provider";
+            }
+            state.setCached(state.isCached() & cstate.isCached());
+        }
+
+        if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) {
+            if (adjType == null) {
+                adjType = "provider";
+            }
+            if (clientProcState == PROCESS_STATE_TOP) {
+                clientProcState = PROCESS_STATE_BOUND_TOP;
+            } else {
+                clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+            }
+        }
+
+        conn.trackProcState(clientProcState, mAdjSeq);
+        if (procState > clientProcState) {
+            procState = clientProcState;
+            state.setCurRawProcState(procState);
+        }
+        if (cstate.getCurrentSchedulingGroup() > schedGroup) {
+            schedGroup = SCHED_GROUP_DEFAULT;
+        }
+        if (adjType != null) {
+            state.setAdjType(adjType);
+            state.setAdjTypeCode(ActivityManager.RunningAppProcessInfo
+                    .REASON_PROVIDER_IN_USE);
+            state.setAdjSource(client);
+            state.setAdjSourceProcState(clientProcState);
+            state.setAdjTarget(conn.provider.name);
+            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+                reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
+                        + ": " + app + ", due to " + client
+                        + " adj=" + adj + " procState="
+                        + ProcessList.makeProcStateString(procState));
+            }
+        }
+
+        // Procstates below BFGS should never have this capability.
+        if (procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
+            capability &= ~PROCESS_CAPABILITY_BFSL;
+        }
+
+        if (adj < prevRawAdj) {
+            schedGroup = setIntermediateAdjLSP(app, adj, prevRawAdj, schedGroup);
+        }
+        if (procState < prevProcState) {
+            setIntermediateProcStateLSP(app, procState, prevProcState);
+        }
+        if (schedGroup > prevSchedGroup) {
+            setIntermediateSchedGroupLSP(state, schedGroup);
+        }
+        state.setCurCapability(capability);
+
+        state.setEmpty(false);
+    }
+
+    protected int getDefaultCapability(ProcessRecord app, int procState) {
         final int networkCapabilities =
                 NetworkPolicyManager.getDefaultProcessNetworkCapabilities(procState);
         final int baseCapabilities;
@@ -2882,7 +3098,7 @@
     /**
      * @return the BFSL capability from a client (of a service binding or provider).
      */
-    int getBfslCapabilityFromClient(ProcessRecord client) {
+    protected int getBfslCapabilityFromClient(ProcessRecord client) {
         // Procstates above FGS should always have this flag. We shouldn't need this logic,
         // but let's do it just in case.
         if (client.mState.getCurProcState() < PROCESS_STATE_FOREGROUND_SERVICE) {
@@ -2967,7 +3183,7 @@
 
     /** Inform the oomadj observer of changes to oomadj. Used by tests. */
     @GuardedBy("mService")
-    private void reportOomAdjMessageLocked(String tag, String msg) {
+    protected void reportOomAdjMessageLocked(String tag, String msg) {
         Slog.d(tag, msg);
         synchronized (mService.mOomAdjObserverLock) {
             if (mService.mCurOomAdjObserver != null) {
@@ -2983,7 +3199,7 @@
 
     /** Applies the computed oomadj, procstate and sched group values and freezes them in set* */
     @GuardedBy({"mService", "mProcLock"})
-    private boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now,
+    protected boolean applyOomAdjLSP(ProcessRecord app, boolean doingAll, long now,
             long nowElapsed, @OomAdjReason int oomAdjReson) {
         boolean success = true;
         final ProcessStateRecord state = app.mState;
@@ -3272,6 +3488,8 @@
         int initialCapability =  PROCESS_CAPABILITY_NONE;
         boolean initialCached = true;
         final ProcessStateRecord state = app.mState;
+        final int prevProcState = PROCESS_STATE_UNKNOWN;
+        final int prevAdj = UNKNOWN_ADJ;
         // If the process has been marked as foreground, it is starting as the top app (with
         // Zygote#START_AS_TOP_APP_ARG), so boost the thread priority of its default UI thread.
         if (state.hasForegroundActivities()) {
@@ -3306,6 +3524,9 @@
         state.setCurRawAdj(ProcessList.FOREGROUND_APP_ADJ);
         state.setForcingToImportant(null);
         state.setHasShownUi(false);
+
+        onProcessStateChanged(app, prevProcState);
+        onProcessOomAdjChanged(app, prevAdj);
     }
 
     // ONLY used for unit testing in OomAdjusterTests.java
@@ -3553,4 +3774,56 @@
         }
         processes.clear();
     }
+
+    @GuardedBy("mService")
+    void onProcessBeginLocked(@NonNull ProcessRecord app) {
+        // Empty, the OomAdjusterModernImpl will have an implementation.
+    }
+
+    @GuardedBy("mService")
+    void onProcessEndLocked(@NonNull ProcessRecord app) {
+        // Empty, the OomAdjusterModernImpl will have an implementation.
+    }
+
+    /**
+     * Called when the process state is changed outside of the OomAdjuster.
+     */
+    @GuardedBy("mService")
+    void onProcessStateChanged(@NonNull ProcessRecord app, int prevProcState) {
+        // Empty, the OomAdjusterModernImpl will have an implementation.
+    }
+
+    /**
+     * Called when the oom adj is changed outside of the OomAdjuster.
+     */
+    @GuardedBy("mService")
+    void onProcessOomAdjChanged(@NonNull ProcessRecord app, int prevAdj) {
+        // Empty, the OomAdjusterModernImpl will have an implementation.
+    }
+
+    @VisibleForTesting
+    void resetInternal() {
+        // Empty, the OomAdjusterModernImpl will have an implementation.
+    }
+
+    @GuardedBy("mService")
+    protected int getInitialAdj(@NonNull ProcessRecord app) {
+        return app.mState.getCurAdj();
+    }
+
+    @GuardedBy("mService")
+    protected int getInitialProcState(@NonNull ProcessRecord app) {
+        return app.mState.getCurProcState();
+    }
+
+    @GuardedBy("mService")
+    protected int getInitialCapability(@NonNull ProcessRecord app) {
+        return app.mState.getCurCapability();
+    }
+
+    @GuardedBy("mService")
+    protected boolean getInitialIsCurBoundByNonBgRestrictedApp(@NonNull ProcessRecord app) {
+        // The caller will set the initial value in this implementation.
+        return app.mState.isCurBoundByNonBgRestrictedApp();
+    }
 }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.md b/services/core/java/com/android/server/am/OomAdjuster.md
index 16091d1..da5e12e 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.md
+++ b/services/core/java/com/android/server/am/OomAdjuster.md
@@ -130,3 +130,28 @@
   * Iterate the processes from least important to most important ones.
   * A maximum retries of 10 is enforced, while in practice, the maximum retries could reach only 2 to 3.
 
+## The Modern Implementation
+
+As aforementioned, the OomAdjuster makes the computation in a recursive way, while this is inefficient in dealing with the cycles. The overall code complexity should be around **O((1 + num(retries)) * num(procs) * num(binding connections))**. In addition, depending on the ordering of the input, the algorithm may produce different results and sometimes it's wrong.
+
+The new "Modern Implementation" is based on the rationale that, apps can't promote the service/provider it connects to, to a higher bucket than itself. We are introducing a bucket based, breadth first search algorithm, as illustrated below:
+
+```
+for all processes in the process list
+  compute the state of each process, but, excluding its clients
+  put each process to the corresponding bucket according to the state value
+done
+
+for each bucket, starting from the top most to the bottom most
+  for each process in the bucket
+     for each process it binds to
+           if the state of the bindee process could be elevated because of the binding; then
+              move the bindee process to the higher bucket
+           fi
+      done
+  done
+done
+```
+
+The overall code complexity should be around **O(num(procs) * num(binding connections))**, which saves the retry time from the existing algorithm.
+
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
new file mode 100644
index 0000000..b852ef5
--- /dev/null
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -0,0 +1,1125 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.app.ActivityManager.PROCESS_STATE_BACKUP;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_RECENT;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
+import static android.app.ActivityManager.PROCESS_STATE_HOME;
+import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT;
+import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI;
+import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
+import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
+import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
+import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
+import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
+import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
+import static com.android.server.am.ProcessList.FOREGROUND_APP_ADJ;
+import static com.android.server.am.ProcessList.HEAVY_WEIGHT_APP_ADJ;
+import static com.android.server.am.ProcessList.HOME_APP_ADJ;
+import static com.android.server.am.ProcessList.NATIVE_ADJ;
+import static com.android.server.am.ProcessList.PERCEPTIBLE_APP_ADJ;
+import static com.android.server.am.ProcessList.PERCEPTIBLE_LOW_APP_ADJ;
+import static com.android.server.am.ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ;
+import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
+import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
+import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
+import static com.android.server.am.ProcessList.SERVICE_ADJ;
+import static com.android.server.am.ProcessList.SERVICE_B_ADJ;
+import static com.android.server.am.ProcessList.SYSTEM_ADJ;
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
+import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.OomAdjReason;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+/**
+ * A modern implementation of the oom adjuster.
+ */
+public class OomAdjusterModernImpl extends OomAdjuster {
+    static final String TAG = "OomAdjusterModernImpl";
+
+    // The ADJ_SLOT_INVALID is NOT an actual slot.
+    static final int ADJ_SLOT_INVALID = -1;
+    static final int ADJ_SLOT_NATIVE = 0;
+    static final int ADJ_SLOT_SYSTEM = 1;
+    static final int ADJ_SLOT_PERSISTENT_PROC = 2;
+    static final int ADJ_SLOT_PERSISTENT_SERVICE = 3;
+    static final int ADJ_SLOT_FOREGROUND_APP = 4;
+    static final int ADJ_SLOT_PERCEPTIBLE_RECENT_FOREGROUND_APP = 5;
+    static final int ADJ_SLOT_VISIBLE_APP = 6;
+    static final int ADJ_SLOT_PERCEPTIBLE_APP = 7;
+    static final int ADJ_SLOT_PERCEPTIBLE_MEDIUM_APP = 8;
+    static final int ADJ_SLOT_PERCEPTIBLE_LOW_APP = 9;
+    static final int ADJ_SLOT_BACKUP_APP = 10;
+    static final int ADJ_SLOT_HEAVY_WEIGHT_APP = 11;
+    static final int ADJ_SLOT_SERVICE = 12;
+    static final int ADJ_SLOT_HOME_APP = 13;
+    static final int ADJ_SLOT_PREVIOUS_APP = 14;
+    static final int ADJ_SLOT_SERVICE_B = 15;
+    static final int ADJ_SLOT_CACHED_APP = 16;
+    static final int ADJ_SLOT_UNKNOWN = 17;
+
+    @IntDef(prefix = { "ADJ_SLOT_" }, value = {
+        ADJ_SLOT_INVALID,
+        ADJ_SLOT_NATIVE,
+        ADJ_SLOT_SYSTEM,
+        ADJ_SLOT_PERSISTENT_PROC,
+        ADJ_SLOT_PERSISTENT_SERVICE,
+        ADJ_SLOT_FOREGROUND_APP,
+        ADJ_SLOT_PERCEPTIBLE_RECENT_FOREGROUND_APP,
+        ADJ_SLOT_VISIBLE_APP,
+        ADJ_SLOT_PERCEPTIBLE_APP,
+        ADJ_SLOT_PERCEPTIBLE_MEDIUM_APP,
+        ADJ_SLOT_PERCEPTIBLE_LOW_APP,
+        ADJ_SLOT_BACKUP_APP,
+        ADJ_SLOT_HEAVY_WEIGHT_APP,
+        ADJ_SLOT_SERVICE,
+        ADJ_SLOT_HOME_APP,
+        ADJ_SLOT_PREVIOUS_APP,
+        ADJ_SLOT_SERVICE_B,
+        ADJ_SLOT_CACHED_APP,
+        ADJ_SLOT_UNKNOWN,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface AdjSlot{}
+
+    static final int[] ADJ_SLOT_VALUES = new int[] {
+        NATIVE_ADJ,
+        SYSTEM_ADJ,
+        PERSISTENT_PROC_ADJ,
+        PERSISTENT_SERVICE_ADJ,
+        FOREGROUND_APP_ADJ,
+        PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ,
+        VISIBLE_APP_ADJ,
+        PERCEPTIBLE_APP_ADJ,
+        PERCEPTIBLE_MEDIUM_APP_ADJ,
+        PERCEPTIBLE_LOW_APP_ADJ,
+        BACKUP_APP_ADJ,
+        HEAVY_WEIGHT_APP_ADJ,
+        SERVICE_ADJ,
+        HOME_APP_ADJ,
+        PREVIOUS_APP_ADJ,
+        SERVICE_B_ADJ,
+        CACHED_APP_MIN_ADJ,
+        UNKNOWN_ADJ,
+    };
+
+    /**
+     * Note: Always use the raw adj to call this API.
+     */
+    static @AdjSlot int adjToSlot(int adj) {
+        if (adj >= ADJ_SLOT_VALUES[0] && adj <= ADJ_SLOT_VALUES[ADJ_SLOT_VALUES.length - 1]) {
+            // Conduct a binary search, in most of the cases it'll get a hit.
+            final int index = Arrays.binarySearch(ADJ_SLOT_VALUES, adj);
+            if (index >= 0) {
+                return index;
+            }
+            // If not found, the returned index above should be (-(insertion point) - 1),
+            // let's return the first slot that's less than the adj value.
+            return -(index + 1) - 1;
+        }
+        return ADJ_SLOT_VALUES.length - 1;
+    }
+
+    static final int[] PROC_STATE_SLOTS = new int[] {
+        PROCESS_STATE_PERSISTENT, // 0
+        PROCESS_STATE_PERSISTENT_UI,
+        PROCESS_STATE_TOP,
+        PROCESS_STATE_BOUND_TOP,
+        PROCESS_STATE_FOREGROUND_SERVICE,
+        PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
+        PROCESS_STATE_IMPORTANT_FOREGROUND,
+        PROCESS_STATE_IMPORTANT_BACKGROUND,
+        PROCESS_STATE_TRANSIENT_BACKGROUND,
+        PROCESS_STATE_BACKUP,
+        PROCESS_STATE_SERVICE,
+        PROCESS_STATE_RECEIVER,
+        PROCESS_STATE_TOP_SLEEPING,
+        PROCESS_STATE_HEAVY_WEIGHT,
+        PROCESS_STATE_HOME,
+        PROCESS_STATE_LAST_ACTIVITY,
+        PROCESS_STATE_CACHED_ACTIVITY,
+        PROCESS_STATE_CACHED_ACTIVITY_CLIENT,
+        PROCESS_STATE_CACHED_RECENT,
+        PROCESS_STATE_CACHED_EMPTY,
+        PROCESS_STATE_UNKNOWN, // -1
+    };
+
+    static int processStateToSlot(@ActivityManager.ProcessState int state) {
+        if (state >= PROCESS_STATE_PERSISTENT && state <= PROCESS_STATE_CACHED_EMPTY) {
+            return state;
+        }
+        return PROC_STATE_SLOTS.length - 1;
+    }
+
+    /**
+     * A container node in the {@link LinkedProcessRecordList},
+     * holding the references to {@link ProcessRecord}.
+     */
+    static class ProcessRecordNode {
+        static final int NODE_TYPE_PROC_STATE = 0;
+        static final int NODE_TYPE_ADJ = 1;
+
+        @IntDef(prefix = { "NODE_TYPE_" }, value = {
+            NODE_TYPE_PROC_STATE,
+            NODE_TYPE_ADJ,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface NodeType {}
+
+        static final int NUM_NODE_TYPE = NODE_TYPE_ADJ + 1;
+
+        @Nullable ProcessRecordNode mPrev;
+        @Nullable ProcessRecordNode mNext;
+        final @Nullable ProcessRecord mApp;
+
+        ProcessRecordNode(@Nullable ProcessRecord app) {
+            mApp = app;
+        }
+
+        void unlink() {
+            if (mPrev != null) {
+                mPrev.mNext = mNext;
+            }
+            if (mNext != null) {
+                mNext.mPrev = mPrev;
+            }
+            mPrev = mNext = null;
+        }
+
+        boolean isLinked() {
+            return mPrev != null && mNext != null;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("ProcessRecordNode{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(' ');
+            sb.append(mApp);
+            sb.append(' ');
+            sb.append(mApp != null ? mApp.mState.getCurProcState() : PROCESS_STATE_UNKNOWN);
+            sb.append(' ');
+            sb.append(mApp != null ? mApp.mState.getCurAdj() : UNKNOWN_ADJ);
+            sb.append(' ');
+            sb.append(Integer.toHexString(System.identityHashCode(mPrev)));
+            sb.append(' ');
+            sb.append(Integer.toHexString(System.identityHashCode(mNext)));
+            sb.append('}');
+            return sb.toString();
+        }
+    }
+
+    private class ProcessRecordNodes {
+        private final @ProcessRecordNode.NodeType int mType;
+
+        private final LinkedProcessRecordList[] mProcessRecordNodes;
+        // The last node besides the tail.
+        private final ProcessRecordNode[] mLastNode;
+
+        ProcessRecordNodes(@ProcessRecordNode.NodeType int type, int size) {
+            mType = type;
+            mProcessRecordNodes = new LinkedProcessRecordList[size];
+            for (int i = 0; i < size; i++) {
+                mProcessRecordNodes[i] = new LinkedProcessRecordList(type);
+            }
+            mLastNode = new ProcessRecordNode[size];
+        }
+
+        int size() {
+            return mProcessRecordNodes.length;
+        }
+
+        @VisibleForTesting
+        void reset() {
+            for (int i = 0; i < mProcessRecordNodes.length; i++) {
+                mProcessRecordNodes[i].reset();
+                mLastNode[i] = null;
+            }
+        }
+
+        void resetLastNodes() {
+            for (int i = 0; i < mProcessRecordNodes.length; i++) {
+                mLastNode[i] = mProcessRecordNodes[i].getLastNodeBeforeTail();
+            }
+        }
+
+        void setLastNodeToHead(int slot) {
+            mLastNode[slot] = mProcessRecordNodes[slot].HEAD;
+        }
+
+        void forEachNewNode(int slot, @NonNull Consumer<OomAdjusterArgs> callback) {
+            ProcessRecordNode node = mLastNode[slot].mNext;
+            final ProcessRecordNode tail = mProcessRecordNodes[slot].TAIL;
+            while (node != tail) {
+                mTmpOomAdjusterArgs.mApp = node.mApp;
+                // Save the next before calling callback, since that may change the node.mNext.
+                final ProcessRecordNode next = node.mNext;
+                callback.accept(mTmpOomAdjusterArgs);
+                // There are couple of cases:
+                // a) The current node is moved to another slot
+                //    - for this case, we'd need to keep using the "next" node.
+                // b) There are one or more new nodes being appended to this slot
+                //    - for this case, we'd need to make sure we scan the new node too.
+                // Based on the assumption that case a) is only possible with
+                // the computeInitialOomAdjLSP(), where the movings are for single node only,
+                // we may safely assume that, if the "next" used to be the "tail" here, and it's
+                // now a new tail somewhere else, that's case a); otherwise, it's case b);
+                node = next == tail && node.mNext != null && node.mNext.mNext != null
+                        ? node.mNext : next;
+            }
+        }
+
+        int getNumberOfSlots() {
+            return mProcessRecordNodes.length;
+        }
+
+        void moveAppTo(@NonNull ProcessRecord app, int prevSlot, int newSlot) {
+            final ProcessRecordNode node = app.mLinkedNodes[mType];
+            if (prevSlot != ADJ_SLOT_INVALID) {
+                if (mLastNode[prevSlot] == node) {
+                    mLastNode[prevSlot] = node.mPrev;
+                }
+                node.unlink();
+            }
+            mProcessRecordNodes[newSlot].append(node);
+        }
+
+        void moveAllNodesTo(int fromSlot, int toSlot) {
+            final LinkedProcessRecordList fromList = mProcessRecordNodes[fromSlot];
+            final LinkedProcessRecordList toList = mProcessRecordNodes[toSlot];
+            if (fromSlot != toSlot && fromList.HEAD.mNext != fromList.TAIL) {
+                fromList.moveTo(toList);
+                mLastNode[fromSlot] = fromList.getLastNodeBeforeTail();
+            }
+        }
+
+        void moveAppToTail(ProcessRecord app) {
+            final ProcessRecordNode node = app.mLinkedNodes[mType];
+            int slot;
+            switch (mType) {
+                case ProcessRecordNode.NODE_TYPE_PROC_STATE:
+                    slot = processStateToSlot(app.mState.getCurProcState());
+                    if (mLastNode[slot] == node) {
+                        mLastNode[slot] = node.mPrev;
+                    }
+                    mProcessRecordNodes[slot].moveNodeToTail(node);
+                    break;
+                case ProcessRecordNode.NODE_TYPE_ADJ:
+                    slot = adjToSlot(app.mState.getCurRawAdj());
+                    if (mLastNode[slot] == node) {
+                        mLastNode[slot] = node.mPrev;
+                    }
+                    mProcessRecordNodes[slot].moveNodeToTail(node);
+                    break;
+                default:
+                    return;
+            }
+
+        }
+
+        void reset(int slot) {
+            mProcessRecordNodes[slot].reset();
+        }
+
+        void unlink(@NonNull ProcessRecord app) {
+            final ProcessRecordNode node = app.mLinkedNodes[mType];
+            final int slot = getCurrentSlot(app);
+            if (slot != ADJ_SLOT_INVALID) {
+                if (mLastNode[slot] == node) {
+                    mLastNode[slot] = node.mPrev;
+                }
+            }
+            node.unlink();
+        }
+
+        void append(@NonNull ProcessRecord app) {
+            append(app, getCurrentSlot(app));
+        }
+
+        void append(@NonNull ProcessRecord app, int targetSlot) {
+            final ProcessRecordNode node = app.mLinkedNodes[mType];
+            mProcessRecordNodes[targetSlot].append(node);
+        }
+
+        private int getCurrentSlot(@NonNull ProcessRecord app) {
+            switch (mType) {
+                case ProcessRecordNode.NODE_TYPE_PROC_STATE:
+                    return processStateToSlot(app.mState.getCurProcState());
+                case ProcessRecordNode.NODE_TYPE_ADJ:
+                    return adjToSlot(app.mState.getCurRawAdj());
+            }
+            return ADJ_SLOT_INVALID;
+        }
+
+        String toString(int slot, int logUid) {
+            return "lastNode=" + mLastNode[slot] + " " + mProcessRecordNodes[slot].toString(logUid);
+        }
+
+        /**
+         * A simple version of {@link java.util.LinkedList}, as here we don't allocate new node
+         * while adding an object to it.
+         */
+        private static class LinkedProcessRecordList {
+            // Sentinel head/tail, to make bookkeeping work easier.
+            final ProcessRecordNode HEAD = new ProcessRecordNode(null);
+            final ProcessRecordNode TAIL = new ProcessRecordNode(null);
+            final @ProcessRecordNode.NodeType int mNodeType;
+
+            LinkedProcessRecordList(@ProcessRecordNode.NodeType int nodeType) {
+                HEAD.mNext = TAIL;
+                TAIL.mPrev = HEAD;
+                mNodeType = nodeType;
+            }
+
+            void append(@NonNull ProcessRecordNode node) {
+                node.mNext = TAIL;
+                node.mPrev = TAIL.mPrev;
+                TAIL.mPrev.mNext = node;
+                TAIL.mPrev = node;
+            }
+
+            void moveTo(@NonNull LinkedProcessRecordList toList) {
+                if (HEAD.mNext != TAIL) {
+                    toList.TAIL.mPrev.mNext = HEAD.mNext;
+                    HEAD.mNext.mPrev = toList.TAIL.mPrev;
+                    toList.TAIL.mPrev = TAIL.mPrev;
+                    TAIL.mPrev.mNext = toList.TAIL;
+                    HEAD.mNext = TAIL;
+                    TAIL.mPrev = HEAD;
+                }
+            }
+
+            void moveNodeToTail(@NonNull ProcessRecordNode node) {
+                node.unlink();
+                append(node);
+            }
+
+            @NonNull ProcessRecordNode getLastNodeBeforeTail() {
+                return TAIL.mPrev;
+            }
+
+            @VisibleForTesting
+            void reset() {
+                HEAD.mNext = TAIL;
+                TAIL.mPrev = HEAD;
+            }
+
+            String toString(int logUid) {
+                final StringBuilder sb = new StringBuilder();
+                sb.append("LinkedProcessRecordList{");
+                sb.append(HEAD);
+                sb.append(' ');
+                sb.append(TAIL);
+                sb.append('[');
+                ProcessRecordNode node = HEAD.mNext;
+                while (node != TAIL) {
+                    if (node.mApp != null && node.mApp.uid == logUid) {
+                        sb.append(node);
+                        sb.append(',');
+                    }
+                    node = node.mNext;
+                }
+                sb.append(']');
+                sb.append('}');
+                return sb.toString();
+            }
+        }
+    }
+
+    /**
+     * A data class for holding the parameters in computing oom adj.
+     */
+    private class OomAdjusterArgs {
+        ProcessRecord mApp;
+        ProcessRecord mTopApp;
+        long mNow;
+        int mCachedAdj;
+        @OomAdjReason int mOomAdjReason;
+        @NonNull ActiveUids mUids;
+        boolean mFullUpdate;
+
+        void update(ProcessRecord topApp, long now, int cachedAdj,
+                @OomAdjReason int oomAdjReason, @NonNull ActiveUids uids, boolean fullUpdate) {
+            mTopApp = topApp;
+            mNow = now;
+            mCachedAdj = cachedAdj;
+            mOomAdjReason = oomAdjReason;
+            mUids = uids;
+            mFullUpdate = fullUpdate;
+        }
+    }
+
+    OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
+            ActiveUids activeUids) {
+        this(service, processList, activeUids, createAdjusterThread());
+    }
+
+    OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
+            ActiveUids activeUids, ServiceThread adjusterThread) {
+        super(service, processList, activeUids, adjusterThread);
+    }
+
+    private final ProcessRecordNodes mProcessRecordProcStateNodes = new ProcessRecordNodes(
+            ProcessRecordNode.NODE_TYPE_PROC_STATE, PROC_STATE_SLOTS.length);
+    private final ProcessRecordNodes mProcessRecordAdjNodes = new ProcessRecordNodes(
+            ProcessRecordNode.NODE_TYPE_ADJ, ADJ_SLOT_VALUES.length);
+    private final OomAdjusterArgs mTmpOomAdjusterArgs = new OomAdjusterArgs();
+
+    void linkProcessRecordToList(@NonNull ProcessRecord app) {
+        mProcessRecordProcStateNodes.append(app);
+        mProcessRecordAdjNodes.append(app);
+    }
+
+    void unlinkProcessRecordFromList(@NonNull ProcessRecord app) {
+        mProcessRecordProcStateNodes.unlink(app);
+        mProcessRecordAdjNodes.unlink(app);
+    }
+
+    @Override
+    @VisibleForTesting
+    void resetInternal() {
+        mProcessRecordProcStateNodes.reset();
+        mProcessRecordAdjNodes.reset();
+    }
+
+    @GuardedBy("mService")
+    @Override
+    void onProcessBeginLocked(@NonNull ProcessRecord app) {
+        // Check one type should be good enough.
+        if (app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE] == null) {
+            for (int i = 0; i < app.mLinkedNodes.length; i++) {
+                app.mLinkedNodes[i] = new ProcessRecordNode(app);
+            }
+        }
+        if (!app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE].isLinked()) {
+            linkProcessRecordToList(app);
+        }
+    }
+
+    @GuardedBy("mService")
+    @Override
+    void onProcessEndLocked(@NonNull ProcessRecord app) {
+        if (app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE] != null
+                && app.mLinkedNodes[ProcessRecordNode.NODE_TYPE_PROC_STATE].isLinked()) {
+            unlinkProcessRecordFromList(app);
+        }
+    }
+
+    @GuardedBy("mService")
+    @Override
+    void onProcessStateChanged(@NonNull ProcessRecord app, int prevProcState) {
+        updateProcStateSlotIfNecessary(app, prevProcState);
+    }
+
+    @GuardedBy("mService")
+    void onProcessOomAdjChanged(@NonNull ProcessRecord app, int prevAdj) {
+        updateAdjSlotIfNecessary(app, prevAdj);
+    }
+
+    @GuardedBy("mService")
+    @Override
+    protected int getInitialAdj(@NonNull ProcessRecord app) {
+        return UNKNOWN_ADJ;
+    }
+
+    @GuardedBy("mService")
+    @Override
+    protected int getInitialProcState(@NonNull ProcessRecord app) {
+        return PROCESS_STATE_UNKNOWN;
+    }
+
+    @GuardedBy("mService")
+    @Override
+    protected int getInitialCapability(@NonNull ProcessRecord app) {
+        return 0;
+    }
+
+    @GuardedBy("mService")
+    @Override
+    protected boolean getInitialIsCurBoundByNonBgRestrictedApp(@NonNull ProcessRecord app) {
+        return false;
+    }
+
+    private void updateAdjSlotIfNecessary(ProcessRecord app, int prevRawAdj) {
+        if (app.mState.getCurRawAdj() != prevRawAdj) {
+            final int slot = adjToSlot(app.mState.getCurRawAdj());
+            final int prevSlot = adjToSlot(prevRawAdj);
+            if (slot != prevSlot && slot != ADJ_SLOT_INVALID) {
+                mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot);
+            }
+        }
+    }
+
+    private void updateProcStateSlotIfNecessary(ProcessRecord app, int prevProcState) {
+        if (app.mState.getCurProcState() != prevProcState) {
+            final int slot = processStateToSlot(app.mState.getCurProcState());
+            final int prevSlot = processStateToSlot(prevProcState);
+            if (slot != prevSlot) {
+                mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot);
+            }
+        }
+    }
+
+    @Override
+    protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+        final ProcessRecord topApp = mService.getTopApp();
+
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
+        mService.mOomAdjProfiler.oomAdjStarted();
+        mAdjSeq++;
+
+        final ProcessStateRecord state = app.mState;
+        final int oldAdj = state.getCurRawAdj();
+        final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ
+                ? oldAdj : UNKNOWN_ADJ;
+
+        final ActiveUids uids = mTmpUidRecords;
+        final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
+        final ArrayList<ProcessRecord> reachableProcesses = mTmpProcessList;
+        final long now = SystemClock.uptimeMillis();
+        final long nowElapsed = SystemClock.elapsedRealtime();
+
+        uids.clear();
+        targetProcesses.clear();
+        targetProcesses.add(app);
+        reachableProcesses.clear();
+
+        // Find out all reachable processes from this app.
+        collectReachableProcessesLocked(targetProcesses, reachableProcesses, uids);
+
+        // Copy all of the reachable processes into the target process set.
+        targetProcesses.addAll(reachableProcesses);
+        reachableProcesses.clear();
+
+        final boolean result = performNewUpdateOomAdjLSP(oomAdjReason,
+                topApp, targetProcesses, uids, false, now, cachedAdj);
+
+        reachableProcesses.addAll(targetProcesses);
+        assignCachedAdjIfNecessary(reachableProcesses);
+        for (int  i = uids.size() - 1; i >= 0; i--) {
+            final UidRecord uidRec = uids.valueAt(i);
+            uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP);
+        }
+        updateUidsLSP(uids, nowElapsed);
+        for (int i = 0, size = targetProcesses.size(); i < size; i++) {
+            applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason);
+        }
+        targetProcesses.clear();
+        reachableProcesses.clear();
+
+        mService.mOomAdjProfiler.oomAdjEnded();
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        return result;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    @Override
+    protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
+            ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
+            boolean startProfiling) {
+        final boolean fullUpdate = processes == null;
+        final ArrayList<ProcessRecord> activeProcesses = fullUpdate
+                ? mProcessList.getLruProcessesLOSP() : processes;
+        ActiveUids activeUids = uids;
+        if (activeUids == null) {
+            final int numUids = mActiveUids.size();
+            activeUids = mTmpUidRecords;
+            activeUids.clear();
+            for (int i = 0; i < numUids; i++) {
+                UidRecord uidRec = mActiveUids.valueAt(i);
+                activeUids.put(uidRec.getUid(), uidRec);
+            }
+        }
+
+        if (startProfiling) {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
+            mService.mOomAdjProfiler.oomAdjStarted();
+        }
+        final long now = SystemClock.uptimeMillis();
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
+        final int numProc = activeProcesses.size();
+
+        mAdjSeq++;
+        if (fullUpdate) {
+            mNewNumServiceProcs = 0;
+            mNewNumAServiceProcs = 0;
+        }
+
+        final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
+        targetProcesses.clear();
+        if (!fullUpdate) {
+            targetProcesses.addAll(activeProcesses);
+        }
+
+        performNewUpdateOomAdjLSP(oomAdjReason, topApp, targetProcesses, activeUids,
+                fullUpdate, now, UNKNOWN_ADJ);
+
+        if (fullUpdate) {
+            assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+            postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
+        } else {
+            activeProcesses.clear();
+            activeProcesses.addAll(targetProcesses);
+            assignCachedAdjIfNecessary(activeProcesses);
+
+            for (int  i = activeUids.size() - 1; i >= 0; i--) {
+                final UidRecord uidRec = activeUids.valueAt(i);
+                uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP);
+            }
+            updateUidsLSP(activeUids, nowElapsed);
+
+            for (int i = 0, size = targetProcesses.size(); i < size; i++) {
+                applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason);
+            }
+
+            activeProcesses.clear();
+        }
+        targetProcesses.clear();
+
+        if (startProfiling) {
+            mService.mOomAdjProfiler.oomAdjEnded();
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        }
+        return;
+    }
+
+    /**
+     * Perform the oom adj update on the given {@code targetProcesses}.
+     *
+     * <p>Note: The expectation to the given {@code targetProcesses} is, the caller
+     * must have called {@link collectReachableProcessesLocked} on it.
+     */
+    private boolean performNewUpdateOomAdjLSP(@OomAdjReason int oomAdjReason,
+            ProcessRecord topApp,  ArraySet<ProcessRecord> targetProcesses, ActiveUids uids,
+            boolean fullUpdate, long now, int cachedAdj) {
+
+        final ArrayList<ProcessRecord> clientProcesses = mTmpProcessList2;
+        clientProcesses.clear();
+
+        // We'll need to collect the upstream processes of the target apps here, because those
+        // processes would potentially impact the procstate/adj via bindings.
+        if (!fullUpdate) {
+            final boolean containsCycle = collectReversedReachableProcessesLocked(targetProcesses,
+                    clientProcesses);
+
+            // If any of its upstream processes are in a cycle,
+            // move them into the candidate targets.
+            if (containsCycle) {
+                // Add all client apps to the target process list.
+                for (int i = 0, size = clientProcesses.size(); i < size; i++) {
+                    final ProcessRecord client = clientProcesses.get(i);
+                    final UidRecord uidRec = client.getUidRecord();
+                    targetProcesses.add(client);
+                    if (uidRec != null) {
+                        uids.put(uidRec.getUid(), uidRec);
+                    }
+                }
+                clientProcesses.clear();
+            }
+            for (int i = 0, size = targetProcesses.size(); i < size; i++) {
+                final ProcessRecord app = targetProcesses.valueAt(i);
+                app.mState.resetCachedInfo();
+                final UidRecord uidRec = app.getUidRecord();
+                if (uidRec != null) {
+                    if (DEBUG_UID_OBSERVERS) {
+                        Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
+                    }
+                    uidRec.reset();
+                }
+            }
+        } else {
+            final ArrayList<ProcessRecord> lru = mProcessList.getLruProcessesLOSP();
+            for (int i = 0, size = lru.size(); i < size; i++) {
+                final ProcessRecord app = lru.get(i);
+                app.mState.resetCachedInfo();
+                final UidRecord uidRec = app.getUidRecord();
+                if (uidRec != null) {
+                    if (DEBUG_UID_OBSERVERS) {
+                        Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
+                    }
+                    uidRec.reset();
+                }
+            }
+        }
+
+        updateNewOomAdjInnerLSP(oomAdjReason, topApp, targetProcesses, clientProcesses, uids,
+                cachedAdj, now, fullUpdate);
+
+        clientProcesses.clear();
+
+        return true;
+    }
+
+    /**
+     * Collect the reversed reachable processes from the given {@code apps}, the result will be
+     * returned in the given {@code processes}, which will <em>NOT</em> include the processes from
+     * the given {@code apps}.
+     */
+    @GuardedBy("mService")
+    private boolean collectReversedReachableProcessesLocked(ArraySet<ProcessRecord> apps,
+            ArrayList<ProcessRecord> clientProcesses) {
+        final ArrayDeque<ProcessRecord> queue = mTmpQueue;
+        queue.clear();
+        clientProcesses.clear();
+        for (int i = 0, size = apps.size(); i < size; i++) {
+            final ProcessRecord app = apps.valueAt(i);
+            app.mState.setReachable(true);
+            app.mState.setReversedReachable(true);
+            queue.offer(app);
+        }
+
+        // Track if any of them reachables could include a cycle
+        boolean containsCycle = false;
+
+        // Scan upstreams of the process record
+        for (ProcessRecord pr = queue.poll(); pr != null; pr = queue.poll()) {
+            if (!pr.mState.isReachable()) {
+                // If not in the given initial set of apps, add it.
+                clientProcesses.add(pr);
+            }
+            final ProcessServiceRecord psr = pr.mServices;
+            for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
+                final ServiceRecord s = psr.getRunningServiceAt(i);
+                final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
+                        s.getConnections();
+                for (int j = serviceConnections.size() - 1; j >= 0; j--) {
+                    final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j);
+                    for (int k = clist.size() - 1; k >= 0; k--) {
+                        final ConnectionRecord cr = clist.get(k);
+                        final ProcessRecord client = cr.binding.client;
+                        containsCycle |= client.mState.isReversedReachable();
+                        if (client.mState.isReversedReachable()) {
+                            continue;
+                        }
+                        queue.offer(client);
+                        client.mState.setReversedReachable(true);
+                    }
+                }
+            }
+            final ProcessProviderRecord ppr = pr.mProviders;
+            for (int i = ppr.numberOfProviders() - 1; i >= 0; i--) {
+                final ContentProviderRecord cpr = ppr.getProviderAt(i);
+                for (int j = cpr.connections.size() - 1; j >= 0; j--) {
+                    final ContentProviderConnection conn = cpr.connections.get(j);
+                    final ProcessRecord client = conn.client;
+                    containsCycle |= client.mState.isReversedReachable();
+                    if (client.mState.isReversedReachable()) {
+                        continue;
+                    }
+                    queue.offer(client);
+                    client.mState.setReversedReachable(true);
+                }
+            }
+            // If this process is a sandbox itself, also add the app on whose behalf
+            // its running
+            if (pr.isSdkSandbox) {
+                for (int is = psr.numberOfRunningServices() - 1; is >= 0; is--) {
+                    ServiceRecord s = psr.getRunningServiceAt(is);
+                    ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
+                            s.getConnections();
+                    for (int conni = serviceConnections.size() - 1; conni >= 0; conni--) {
+                        ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni);
+                        for (int i = clist.size() - 1; i >= 0; i--) {
+                            ConnectionRecord cr = clist.get(i);
+                            ProcessRecord attributedApp = cr.binding.attributedClient;
+                            if (attributedApp == null || attributedApp == pr) {
+                                continue;
+                            }
+                            containsCycle |= attributedApp.mState.isReversedReachable();
+                            if (attributedApp.mState.isReversedReachable()) {
+                                continue;
+                            }
+                            queue.offer(attributedApp);
+                            attributedApp.mState.setReversedReachable(true);
+                        }
+                    }
+                }
+            }
+        }
+
+        // Reset the temporary bits.
+        for (int i = clientProcesses.size() - 1; i >= 0; i--) {
+            clientProcesses.get(i).mState.setReversedReachable(false);
+        }
+        for (int i = 0, size = apps.size(); i < size; i++) {
+            final ProcessRecord app = apps.valueAt(i);
+            app.mState.setReachable(false);
+            app.mState.setReversedReachable(false);
+        }
+        return containsCycle;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    private void updateNewOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
+            ArraySet<ProcessRecord> targetProcesses, ArrayList<ProcessRecord> clientProcesses,
+            ActiveUids uids, int cachedAdj, long now, boolean fullUpdate) {
+        mTmpOomAdjusterArgs.update(topApp, now, cachedAdj, oomAdjReason, uids, fullUpdate);
+
+        mProcessRecordProcStateNodes.resetLastNodes();
+        mProcessRecordAdjNodes.resetLastNodes();
+
+        final int procStateTarget = mProcessRecordProcStateNodes.size() - 1;
+        final int adjTarget = mProcessRecordAdjNodes.size() - 1;
+
+        final int appUid = !fullUpdate && targetProcesses.size() > 0
+                ? targetProcesses.valueAt(0).uid : -1;
+        final int logUid = mService.mCurOomAdjUid;
+
+        mAdjSeq++;
+        // All apps to be updated will be moved to the lowest slot.
+        if (fullUpdate) {
+            // Move all the process record node to the lowest slot, we'll do recomputation on all of
+            // them. Use the processes from the lru list, because the scanning order matters here.
+            final ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
+            for (int i = procStateTarget; i >= 0; i--) {
+                mProcessRecordProcStateNodes.reset(i);
+                // Force the last node to the head since we'll recompute all of them.
+                mProcessRecordProcStateNodes.setLastNodeToHead(i);
+            }
+            // enqueue the targets in the reverse order of the lru list.
+            for (int i = lruList.size() - 1; i >= 0; i--) {
+                mProcessRecordProcStateNodes.append(lruList.get(i), procStateTarget);
+            }
+            // Do the same to the adj nodes.
+            for (int i = adjTarget; i >= 0; i--) {
+                mProcessRecordAdjNodes.reset(i);
+                // Force the last node to the head since we'll recompute all of them.
+                mProcessRecordAdjNodes.setLastNodeToHead(i);
+            }
+            for (int i = lruList.size() - 1; i >= 0; i--) {
+                mProcessRecordAdjNodes.append(lruList.get(i), adjTarget);
+            }
+        } else {
+            // Move the target processes to the lowest slot.
+            for (int i = 0, size = targetProcesses.size(); i < size; i++) {
+                final ProcessRecord app = targetProcesses.valueAt(i);
+                final int procStateSlot = processStateToSlot(app.mState.getCurProcState());
+                final int adjSlot = adjToSlot(app.mState.getCurRawAdj());
+                mProcessRecordProcStateNodes.moveAppTo(app, procStateSlot, procStateTarget);
+                mProcessRecordAdjNodes.moveAppTo(app, adjSlot, adjTarget);
+            }
+            // Move the "lastNode" to head to make sure we scan all nodes in this slot.
+            mProcessRecordProcStateNodes.setLastNodeToHead(procStateTarget);
+            mProcessRecordAdjNodes.setLastNodeToHead(adjTarget);
+        }
+
+        // All apps to be updated have been moved to the lowest slot.
+        // Do an initial pass of the computation.
+        mProcessRecordProcStateNodes.forEachNewNode(mProcessRecordProcStateNodes.size() - 1,
+                this::computeInitialOomAdjLSP);
+
+        if (!fullUpdate) {
+            // We didn't update the client processes with the computeInitialOomAdjLSP
+            // because they don't need to do so. But they'll be playing vital roles in
+            // computing the bindings. So include them into the scan list below.
+            for (int i = 0, size = clientProcesses.size(); i < size; i++) {
+                mProcessRecordProcStateNodes.moveAppToTail(clientProcesses.get(i));
+            }
+            // We don't update the adj list since we're resetting it below.
+        }
+
+        // Now nodes are set into their slots, without facting in the bindings.
+        // The nodes between the `lastNode` pointer and the TAIL should be the new nodes.
+        //
+        // The whole rationale here is that, the bindings from client to host app, won't elevate
+        // the host app's procstate/adj higher than the client app's state (BIND_ABOVE_CLIENT
+        // is a special case here, but client app's raw adj is still no less than the host app's).
+        // Therefore, starting from the top to the bottom, for each slot, scan all of the new nodes,
+        // check its bindings, elevate its host app's slot if necessary.
+        //
+        // We'd have to do this in two passes: 1) scan procstate node list; 2) scan adj node list.
+        // Because the procstate and adj are not always in sync - there are cases where
+        // the processes with lower proc state could be getting a higher oom adj score.
+        // And because of this, the procstate and adj node lists are basically two priority heaps.
+        //
+        // As the 2nd pass with the adj node lists potentially includes a significant amount of
+        // duplicated scans as the 1st pass has done, we'll reset the last node pointers for
+        // the adj node list before the 1st pass; so during the 1st pass, if any app's adj slot
+        // gets bumped, we'll only scan those in 2nd pass.
+
+        mProcessRecordAdjNodes.resetLastNodes();
+
+        // 1st pass, scan each slot in the procstate node list.
+        for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) {
+            mProcessRecordProcStateNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
+        }
+
+        // 2nd pass, scan each slot in the adj node list.
+        for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) {
+            mProcessRecordAdjNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
+        }
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    private void computeInitialOomAdjLSP(OomAdjusterArgs args) {
+        final ProcessRecord app = args.mApp;
+        final int cachedAdj = args.mCachedAdj;
+        final ProcessRecord topApp = args.mTopApp;
+        final long now = args.mNow;
+        final int oomAdjReason = args.mOomAdjReason;
+        final ActiveUids uids = args.mUids;
+        final boolean fullUpdate = args.mFullUpdate;
+
+        if (DEBUG_OOM_ADJ) {
+            Slog.i(TAG, "OOM ADJ initial args app=" + app
+                    + " cachedAdj=" + cachedAdj
+                    + " topApp=" + topApp
+                    + " now=" + now
+                    + " oomAdjReason=" + oomAdjReasonToString(oomAdjReason)
+                    + " fullUpdate=" + fullUpdate);
+        }
+
+        if (uids != null) {
+            final UidRecord uidRec = app.getUidRecord();
+
+            if (uidRec != null) {
+                uids.put(uidRec.getUid(), uidRec);
+            }
+        }
+
+        computeOomAdjLSP(app, cachedAdj, topApp, fullUpdate, now, false, false, oomAdjReason,
+                false);
+    }
+
+    /**
+     * @return The proposed change to the schedGroup.
+     */
+    @GuardedBy({"mService", "mProcLock"})
+    @Override
+    protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj,
+            int schedGroup) {
+        schedGroup = super.setIntermediateAdjLSP(app, adj, prevRawAppAdj, schedGroup);
+
+        updateAdjSlotIfNecessary(app, prevRawAppAdj);
+
+        return schedGroup;
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    @Override
+    protected void setIntermediateProcStateLSP(ProcessRecord app, int procState,
+            int prevProcState) {
+        super.setIntermediateProcStateLSP(app, procState, prevProcState);
+
+        updateProcStateSlotIfNecessary(app, prevProcState);
+    }
+
+    @GuardedBy({"mService", "mProcLock"})
+    private void computeHostOomAdjLSP(OomAdjusterArgs args) {
+        final ProcessRecord app = args.mApp;
+        final int cachedAdj = args.mCachedAdj;
+        final ProcessRecord topApp = args.mTopApp;
+        final long now = args.mNow;
+        final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
+        final boolean fullUpdate = args.mFullUpdate;
+        final ActiveUids uids = args.mUids;
+
+        final ProcessServiceRecord psr = app.mServices;
+        for (int i = psr.numberOfConnections() - 1; i >= 0; i--) {
+            ConnectionRecord cr = psr.getConnectionAt(i);
+            ProcessRecord service = cr.hasFlag(ServiceInfo.FLAG_ISOLATED_PROCESS)
+                    ? cr.binding.service.isolationHostProc : cr.binding.service.app;
+            if (service == null || service == app
+                    || (service.mState.getMaxAdj() >= SYSTEM_ADJ
+                            && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+                    || (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
+                            && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+                            && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+                continue;
+            }
+
+
+            computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
+                    oomAdjReason, cachedAdj, false);
+        }
+
+        for (int i = psr.numberOfSdkSandboxConnections() - 1; i >= 0; i--) {
+            final ConnectionRecord cr = psr.getSdkSandboxConnectionAt(i);
+            final ProcessRecord service = cr.binding.service.app;
+            if (service == null || service == app
+                    || (service.mState.getMaxAdj() >= SYSTEM_ADJ
+                            && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+                    || (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
+                            && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+                            && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+                continue;
+            }
+
+            computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
+                    oomAdjReason, cachedAdj, false);
+        }
+
+        final ProcessProviderRecord ppr = app.mProviders;
+        for (int i = ppr.numberOfProviderConnections() - 1; i >= 0; i--) {
+            ContentProviderConnection cpc = ppr.getProviderConnectionAt(i);
+            ProcessRecord provider = cpc.provider.proc;
+            if (provider == null || provider == app
+                    || (provider.mState.getMaxAdj() >= ProcessList.SYSTEM_ADJ
+                            && provider.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+                    || (provider.mState.getCurAdj() <= FOREGROUND_APP_ADJ
+                            && provider.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+                            && provider.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+                continue;
+            }
+
+            computeProviderHostOomAdjLSP(cpc, provider, app, now, topApp, fullUpdate, false, false,
+                    oomAdjReason, cachedAdj, false);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index f532122c1..7037fec 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -22,6 +22,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.am.ActivityManagerService.MY_PID;
+import static com.android.server.am.OomAdjusterModernImpl.ProcessRecordNode.NUM_NODE_TYPE;
 
 import static java.util.Objects.requireNonNull;
 
@@ -63,6 +64,7 @@
 import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.os.Zygote;
 import com.android.server.FgThread;
+import com.android.server.am.OomAdjusterModernImpl.ProcessRecordNode;
 import com.android.server.wm.WindowProcessController;
 import com.android.server.wm.WindowProcessListener;
 
@@ -434,6 +436,8 @@
      */
     volatile boolean mSkipProcessGroupCreation;
 
+    final ProcessRecordNode[] mLinkedNodes = new ProcessRecordNode[NUM_NODE_TYPE];
+
     void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo,
             long startUptime, long startElapsedTime) {
         this.mStartUid = startUid;
@@ -1114,6 +1118,7 @@
         mState.onCleanupApplicationRecordLSP();
         mServices.onCleanupApplicationRecordLocked();
         mReceivers.onCleanupApplicationRecordLocked();
+        mService.mOomAdjuster.onProcessEndLocked(this);
 
         return mProviders.onCleanupApplicationRecordLocked(allowRestart);
     }
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 7ff6d11..a165e88 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -19,6 +19,7 @@
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_BOUND_SERVICE;
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_FOREGROUND_SERVICE;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.ServiceInfo;
@@ -134,6 +135,11 @@
     private final ArraySet<ConnectionRecord> mConnections = new ArraySet<>();
 
     /**
+     * All ConnectionRecord this process holds indirectly to SDK sandbox processes.
+     */
+    private @Nullable ArraySet<ConnectionRecord> mSdkSandboxConnections;
+
+    /**
      * A set of UIDs of all bound clients.
      */
     private ArraySet<Integer> mBoundClientUids = new ArraySet<>();
@@ -490,13 +496,18 @@
 
     void addConnection(ConnectionRecord connection) {
         mConnections.add(connection);
+        addSdkSandboxConnectionIfNecessary(connection);
     }
 
     void removeConnection(ConnectionRecord connection) {
         mConnections.remove(connection);
+        removeSdkSandboxConnectionIfNecessary(connection);
     }
 
     void removeAllConnections() {
+        for (int i = 0, size = mConnections.size(); i < size; i++) {
+            removeSdkSandboxConnectionIfNecessary(mConnections.valueAt(i));
+        }
         mConnections.clear();
     }
 
@@ -508,6 +519,39 @@
         return mConnections.size();
     }
 
+    private void addSdkSandboxConnectionIfNecessary(ConnectionRecord connection) {
+        final ProcessRecord attributedClient = connection.binding.attributedClient;
+        if (attributedClient != null && connection.binding.service.isSdkSandbox) {
+            if (attributedClient.mServices.mSdkSandboxConnections == null) {
+                attributedClient.mServices.mSdkSandboxConnections = new ArraySet<>();
+            }
+            attributedClient.mServices.mSdkSandboxConnections.add(connection);
+        }
+    }
+
+    private void removeSdkSandboxConnectionIfNecessary(ConnectionRecord connection) {
+        final ProcessRecord attributedClient = connection.binding.attributedClient;
+        if (attributedClient != null && connection.binding.service.isSdkSandbox) {
+            if (attributedClient.mServices.mSdkSandboxConnections == null) {
+                attributedClient.mServices.mSdkSandboxConnections.remove(connection);
+            }
+        }
+    }
+
+    void removeAllSdkSandboxConnections() {
+        if (mSdkSandboxConnections != null) {
+            mSdkSandboxConnections.clear();
+        }
+    }
+
+    ConnectionRecord getSdkSandboxConnectionAt(int index) {
+        return mSdkSandboxConnections != null ? mSdkSandboxConnections.valueAt(index) : null;
+    }
+
+    int numberOfSdkSandboxConnections() {
+        return mSdkSandboxConnections != null ? mSdkSandboxConnections.size() : 0;
+    }
+
     void addBoundClientUid(int clientUid, String clientPackageName, long bindFlags) {
         mBoundClientUids.add(clientUid);
         mApp.getWindowProcessController()
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index db341d2..a9c388c 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -378,6 +378,12 @@
     private boolean mReachable;
 
     /**
+     * Whether or not this process is reversed reachable from given process.
+     */
+    @GuardedBy("mService")
+    private boolean mReversedReachable;
+
+    /**
      * The most recent time when the last visible activity within this process became invisible.
      *
      * <p> It'll be set to 0 if there is never a visible activity, or Long.MAX_VALUE if there is
@@ -454,6 +460,9 @@
     @GuardedBy("mService")
     private int mCachedSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
 
+    @GuardedBy("mService")
+    private boolean mScheduleLikeTopApp = false;
+
     ProcessStateRecord(ProcessRecord app) {
         mApp = app;
         mService = app.mService;
@@ -614,9 +623,11 @@
     void forceProcessStateUpTo(int newState) {
         if (mRepProcState > newState) {
             synchronized (mProcLock) {
+                final int prevProcState = mRepProcState;
                 setReportedProcState(newState);
                 setCurProcState(newState);
                 setCurRawProcState(newState);
+                mService.mOomAdjuster.onProcessStateChanged(mApp, prevProcState);
             }
         }
     }
@@ -985,6 +996,16 @@
     }
 
     @GuardedBy("mService")
+    boolean isReversedReachable() {
+        return mReversedReachable;
+    }
+
+    @GuardedBy("mService")
+    void setReversedReachable(boolean reversedReachable) {
+        mReversedReachable = reversedReachable;
+    }
+
+    @GuardedBy("mService")
     void resetCachedInfo() {
         mCachedHasActivities = VALUE_INVALID;
         mCachedIsHeavyWeight = VALUE_INVALID;
@@ -1134,6 +1155,16 @@
         return mCachedSchedGroup;
     }
 
+    @GuardedBy("mService")
+    boolean shouldScheduleLikeTopApp() {
+        return mScheduleLikeTopApp;
+    }
+
+    @GuardedBy("mService")
+    void setScheduleLikeTopApp(boolean scheduleLikeTopApp) {
+        mScheduleLikeTopApp = scheduleLikeTopApp;
+    }
+
     @GuardedBy(anyOf = {"mService", "mProcLock"})
     public String makeAdjReason() {
         if (mAdjSource != null || mAdjTarget != null) {
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 4f7a2ba..6191861 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -80,8 +80,7 @@
     @VisibleForTesting
     static final String UNIQUE_ID_PREFIX = "virtual:";
 
-    private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices =
-            new ArrayMap<IBinder, VirtualDisplayDevice>();
+    private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices = new ArrayMap<>();
     private final Handler mHandler;
     private final SurfaceControlDisplayFactory mSurfaceControlDisplayFactory;
 
@@ -113,9 +112,16 @@
     public DisplayDevice createVirtualDisplayLocked(IVirtualDisplayCallback callback,
             IMediaProjection projection, int ownerUid, String ownerPackageName, Surface surface,
             int flags, VirtualDisplayConfig virtualDisplayConfig) {
+        IBinder appToken = callback.asBinder();
+        if (mVirtualDisplayDevices.containsKey(appToken)) {
+            Slog.wtfStack(TAG,
+                    "Can't create virtual display, display with same appToken already exists");
+            return null;
+        }
+
         String name = virtualDisplayConfig.getName();
         boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
-        IBinder appToken = callback.asBinder();
+
         IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure,
                 virtualDisplayConfig.getRequestedRefreshRate());
         final String baseUniqueId =
diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
index f0bf1ea8..d0c346a 100644
--- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -334,7 +334,10 @@
                             ArtStatsLog.BACKGROUND_DEXOPT_JOB_ENDED__STATUS__STATUS_UNKNOWN),
                     cancellationReason,
                     durationMs,
-                    0);  // deprecated, used to be durationIncludingSleepMs
+                    0, // deprecated, used to be durationIncludingSleepMs
+                    0, // optimizedPackagesCount
+                    0, // packagesDependingOnBootClasspathCount
+                    0); // totalPackagesCount
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 2c866ab..8673b90 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.BIND_VOICE_INTERACTION;
 import static android.Manifest.permission.CHANGE_CONFIGURATION;
+import static android.Manifest.permission.CONTROL_KEYGUARD;
 import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
 import static android.Manifest.permission.DETECT_SCREEN_CAPTURE;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
@@ -3549,6 +3550,7 @@
 
     @Override
     public void keyguardGoingAway(int flags) {
+        mAmInternal.enforceCallingPermission(CONTROL_KEYGUARD, "unlock keyguard");
         enforceNotIsolatedCaller("keyguardGoingAway");
         final long token = Binder.clearCallingIdentity();
         try {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6d0fd2a..029f46f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5682,6 +5682,12 @@
             // TODO(b/233286785): Add sync support to wallpaper.
             return true;
         }
+        if (mActivityRecord != null && mViewVisibility != View.VISIBLE
+                && mWinAnimator.mAttrType != TYPE_BASE_APPLICATION
+                && mWinAnimator.mAttrType != TYPE_APPLICATION_STARTING) {
+            // Skip sync for invisible app windows which are not managed by activity lifecycle.
+            return false;
+        }
         // In the WindowContainer implementation we immediately mark ready
         // since a generic WindowContainer only needs to wait for its
         // children to finish and is immediately ready from its own
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index c0cfa53..486ddb4 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -420,14 +420,11 @@
             setDeviceServer(server);
         }
 
-        @RequiresPermission(anyOf = {Manifest.permission.QUERY_USERS,
-                Manifest.permission.CREATE_USERS,
-                Manifest.permission.MANAGE_USERS})
         public Device(BluetoothDevice bluetoothDevice) {
             mBluetoothDevice = bluetoothDevice;
             mServiceInfo = null;
             mUid = mBluetoothServiceUid;
-            mUserId = mUserManager.getMainUser().getIdentifier();
+            mUserId = UserHandle.getUserId(mUid);
         }
 
         private void setDeviceServer(IMidiDeviceServer server) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
index 6108ad2..ce4aa44 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedList.kt
@@ -16,6 +16,9 @@
 
 package com.android.server.permission.access.immutable
 
+/**
+ * Immutable list with index-based access.
+ */
 sealed class IndexedList<T>(
     internal val list: ArrayList<T>
 ) : Immutable<MutableIndexedList<T>> {
@@ -34,6 +37,9 @@
     override fun toString(): String = list.toString()
 }
 
+/**
+ * Mutable list with index-based access.
+ */
 class MutableIndexedList<T>(
     list: ArrayList<T> = ArrayList()
 ) : IndexedList<T>(list) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
index 1202c81..77e71ba 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedListSet.kt
@@ -16,6 +16,9 @@
 
 package com.android.server.permission.access.immutable
 
+/**
+ * Immutable set with index-based access, implemented using a list.
+ */
 sealed class IndexedListSet<T>(
     internal val list: ArrayList<T>
 ) : Immutable<MutableIndexedListSet<T>> {
@@ -36,6 +39,9 @@
     override fun toString(): String = list.toString()
 }
 
+/**
+ * Mutable set with index-based access, implemented using a list.
+ */
 class MutableIndexedListSet<T>(
     list: ArrayList<T> = ArrayList()
 ) : IndexedListSet<T>(list) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
index 5c75de8..299cc89 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedMap.kt
@@ -18,6 +18,9 @@
 
 import android.util.ArrayMap
 
+/**
+ * Immutable map with index-based access.
+ */
 sealed class IndexedMap<K, V>(
     internal val map: ArrayMap<K, V>
 ) : Immutable<MutableIndexedMap<K, V>> {
@@ -42,6 +45,9 @@
     override fun toString(): String = map.toString()
 }
 
+/**
+ * Mutable map with index-based access.
+ */
 class MutableIndexedMap<K, V>(
     map: ArrayMap<K, V> = ArrayMap()
 ) : IndexedMap<K, V>(map) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
index 8c963aa..ff76a47 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedReferenceMap.kt
@@ -18,6 +18,11 @@
 
 import android.util.ArrayMap
 
+/**
+ * Immutable map with index-based access and mutable data structure values.
+ *
+ * @see MutableReference
+ */
 sealed class IndexedReferenceMap<K, I : Immutable<M>, M : I>(
     internal val map: ArrayMap<K, MutableReference<I, M>>
 ) : Immutable<MutableIndexedReferenceMap<K, I, M>> {
@@ -42,6 +47,11 @@
     override fun toString(): String = map.toString()
 }
 
+/**
+ * Mutable map with index-based access and mutable data structure values.
+ *
+ * @see MutableReference
+ */
 class MutableIndexedReferenceMap<K, I : Immutable<M>, M : I>(
     map: ArrayMap<K, MutableReference<I, M>> = ArrayMap()
 ) : IndexedReferenceMap<K, I, M>(map) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
index 9868616..547e56c 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IndexedSet.kt
@@ -18,6 +18,9 @@
 
 import android.util.ArraySet
 
+/**
+ * Immutable set with index-based access.
+ */
 sealed class IndexedSet<T>(
     internal val set: ArraySet<T>
 ) : Immutable<MutableIndexedSet<T>> {
@@ -37,6 +40,9 @@
     override fun toString(): String = set.toString()
 }
 
+/**
+ * Mutable set with index-based access.
+ */
 class MutableIndexedSet<T>(
     set: ArraySet<T> = ArraySet()
 ) : IndexedSet<T>(set) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
index b7d8b4c..7ed29e8 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntMap.kt
@@ -18,6 +18,9 @@
 
 import android.util.SparseArray
 
+/**
+ * Immutable map with index-based access and [Int] keys.
+ */
 sealed class IntMap<T>(
     internal val array: SparseArray<T>
 ) : Immutable<MutableIntMap<T>> {
@@ -41,6 +44,9 @@
     override fun toString(): String = array.toString()
 }
 
+/**
+ * Mutable map with index-based access and [Int] keys.
+ */
 class MutableIntMap<T>(
     array: SparseArray<T> = SparseArray()
 ) : IntMap<T>(array) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
index 22fa8f2..160b227 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntReferenceMap.kt
@@ -18,6 +18,11 @@
 
 import android.util.SparseArray
 
+/**
+ * Immutable map with index-based access, [Int] keys and mutable data structure values.
+ *
+ * @see MutableReference
+ */
 sealed class IntReferenceMap<I : Immutable<M>, M : I>(
     internal val array: SparseArray<MutableReference<I, M>>
 ) : Immutable<MutableIntReferenceMap<I, M>> {
@@ -42,6 +47,11 @@
     override fun toString(): String = array.toString()
 }
 
+/**
+ * Mutable map with index-based access, [Int] keys and mutable data structure values.
+ *
+ * @see MutableReference
+ */
 class MutableIntReferenceMap<I : Immutable<M>, M : I>(
     array: SparseArray<MutableReference<I, M>> = SparseArray()
 ) : IntReferenceMap<I, M>(array) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
index 9da3671..21f2af2 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/IntSet.kt
@@ -18,6 +18,9 @@
 
 import android.util.SparseBooleanArray
 
+/**
+ * Immutable set with index-based access and [Int] elements.
+ */
 sealed class IntSet(
     internal val array: SparseBooleanArray
 ) : Immutable<MutableIntSet> {
@@ -37,6 +40,9 @@
     override fun toString(): String = array.toString()
 }
 
+/**
+ * Mutable set with index-based access and [Int] elements.
+ */
 class MutableIntSet(
     array: SparseBooleanArray = SparseBooleanArray()
 ) : IntSet(array) {
diff --git a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
index e39a3bb..171cfeb 100644
--- a/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
+++ b/services/permission/java/com/android/server/permission/access/immutable/MutableReference.kt
@@ -16,14 +16,39 @@
 
 package com.android.server.permission.access.immutable
 
+/**
+ * Wrapper class for reference to a mutable data structure instance.
+ *
+ * This class encapsulates the logic to mutate/copy a mutable data structure instance and update the
+ * reference to the new mutated instance. It also remembers the mutated instance so that it can be
+ * reused during further mutations.
+ *
+ * Instances of this class should be kept private within a data structure, with the [get] method
+ * exposed on the immutable interface of the data structure as a `getFoo` method, and the [mutate]
+ * method exposed on the mutable interface of the data structure as a `mutateFoo` method. When the
+ * data structure is mutated/copied, a new instance of this class should be obtained with
+ * [toImmutable], which makes the wrapped reference immutable-only again and thus prevents
+ * further modifications to a data structure accessed with its immutable interface.
+ *
+ * @see MutableIndexedReferenceMap
+ * @see MutableIntReferenceMap
+ */
 class MutableReference<I : Immutable<M>, M : I> private constructor(
     private var immutable: I,
     private var mutable: M?
 ) {
     constructor(mutable: M) : this(mutable, mutable)
 
+    /**
+     * Return an immutable reference to the wrapped mutable data structure.
+     */
     fun get(): I = immutable
 
+    /**
+     * Make the wrapped mutable data structure mutable, by either calling [Immutable.toMutable] and
+     * replacing the wrapped reference with its result, or reusing the existing reference if it's
+     * already mutable.
+     */
     fun mutate(): M {
         mutable?.let { return it }
         return immutable.toMutable().also {
@@ -32,6 +57,10 @@
         }
     }
 
+    /**
+     * Create a new [MutableReference] instance with the wrapped mutable data structure being
+     * immutable-only again.
+     */
     fun toImmutable(): MutableReference<I, M> = MutableReference(immutable, null)
 
     override fun equals(other: Any?): Boolean {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index d16c9c5..bf23117 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -248,6 +248,8 @@
     @Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
     @Mock IVirtualDisplayCallback.Stub mMockAppToken;
     @Mock IVirtualDisplayCallback.Stub mMockAppToken2;
+
+    @Mock IVirtualDisplayCallback.Stub mMockAppToken3;
     @Mock WindowManagerInternal mMockWindowManagerInternal;
     @Mock LightsManager mMockLightsManager;
     @Mock VirtualDisplayAdapter mMockVirtualDisplayAdapter;
@@ -838,6 +840,7 @@
 
         registerDefaultDisplays(displayManager);
         when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2);
 
         IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
         when(virtualDevice.getDeviceId()).thenReturn(1);
@@ -851,7 +854,7 @@
         int displayId1 =
                 localService.createVirtualDisplay(
                         builder1.build(),
-                        mMockAppToken /* callback */,
+                        mMockAppToken2 /* callback */,
                         virtualDevice /* virtualDeviceToken */,
                         mock(DisplayWindowPolicyController.class),
                         PACKAGE_NAME);
@@ -893,6 +896,7 @@
 
         registerDefaultDisplays(displayManager);
         when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2);
 
         IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
         when(virtualDevice.getDeviceId()).thenReturn(1);
@@ -927,7 +931,7 @@
         int displayId2 =
                 localService.createVirtualDisplay(
                         builder2.build(),
-                        mMockAppToken /* callback */,
+                        mMockAppToken2 /* callback */,
                         virtualDevice /* virtualDeviceToken */,
                         mock(DisplayWindowPolicyController.class),
                         PACKAGE_NAME);
@@ -950,6 +954,8 @@
 
         registerDefaultDisplays(displayManager);
         when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        when(mMockAppToken2.asBinder()).thenReturn(mMockAppToken2);
+        when(mMockAppToken3.asBinder()).thenReturn(mMockAppToken3);
 
         IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
         when(virtualDevice.getDeviceId()).thenReturn(1);
@@ -999,7 +1005,7 @@
         int ownDisplayGroupDisplayId =
                 localService.createVirtualDisplay(
                         ownDisplayGroupConfig,
-                        mMockAppToken /* callback */,
+                        mMockAppToken2 /* callback */,
                         virtualDevice /* virtualDeviceToken */,
                         mock(DisplayWindowPolicyController.class),
                         PACKAGE_NAME);
@@ -1024,7 +1030,7 @@
         int defaultDisplayGroupDisplayId =
                 localService.createVirtualDisplay(
                         defaultDisplayGroupConfig,
-                        mMockAppToken /* callback */,
+                        mMockAppToken3 /* callback */,
                         null /* virtualDeviceToken */,
                         mock(DisplayWindowPolicyController.class),
                         PACKAGE_NAME);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index e58ec45..01e49f2 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -59,6 +59,7 @@
 import android.view.DisplayInfo;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -303,6 +304,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 294107062)
     public void testDisplayBrightnessFollowers_BothDpcsSupportNits() {
         DisplayPowerControllerHolder followerDpc =
                 createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -356,6 +358,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 294107062)
     public void testDisplayBrightnessFollowers_FollowerDoesNotSupportNits() {
         DisplayPowerControllerHolder followerDpc =
                 createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -388,6 +391,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 294107062)
     public void testDisplayBrightnessFollowers_LeadDpcDoesNotSupportNits() {
         DisplayPowerControllerHolder followerDpc =
                 createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -418,6 +422,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 294107062)
     public void testDisplayBrightnessFollowers_NeitherDpcSupportsNits() {
         DisplayPowerControllerHolder followerDpc =
                 createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
@@ -450,6 +455,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 294107062)
     public void testDisplayBrightnessFollowers_AutomaticBrightness() {
         Settings.System.putInt(mContext.getContentResolver(),
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
@@ -521,6 +527,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 294107062)
     public void testDisplayBrightnessFollowersRemoval_RemoveSingleFollower() {
         DisplayPowerControllerHolder followerDpc = createDisplayPowerController(FOLLOWER_DISPLAY_ID,
                 FOLLOWER_UNIQUE_ID);
@@ -612,6 +619,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 294107062)
     public void testDisplayBrightnessFollowersRemoval_RemoveAllFollowers() {
         DisplayPowerControllerHolder followerHolder =
                 createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_ID);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
new file mode 100644
index 0000000..8bbacc4
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.display.IVirtualDisplayCallback;
+import android.hardware.display.VirtualDisplayConfig;
+import android.os.IBinder;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.testutils.TestHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VirtualDisplayAdapterTest {
+
+    @Mock
+    Context mContextMock;
+
+    @Mock
+    VirtualDisplayAdapter.SurfaceControlDisplayFactory mMockSufaceControlDisplayFactory;
+
+    @Mock
+    DisplayAdapter.Listener mMockListener;
+
+    @Mock
+    IVirtualDisplayCallback mMockCallback;
+
+    @Mock
+    IBinder mMockBinder;
+
+    private TestHandler mHandler;
+
+    private VirtualDisplayAdapter mVirtualDisplayAdapter;
+
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mHandler = new TestHandler(null);
+        mVirtualDisplayAdapter = new VirtualDisplayAdapter(new DisplayManagerService.SyncRoot(),
+                mContextMock, mHandler, mMockListener, mMockSufaceControlDisplayFactory);
+
+        when(mMockCallback.asBinder()).thenReturn(mMockBinder);
+    }
+
+    @Test
+    public void testCreatesVirtualDisplay() {
+        VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("test", /* width= */ 1,
+                /* height= */ 1, /* densityDpi= */ 1).build();
+
+        DisplayDevice result = mVirtualDisplayAdapter.createVirtualDisplayLocked(mMockCallback,
+                /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage",
+                /* surface= */ null, /* flags= */ 0, config);
+
+        assertNotNull(result);
+    }
+
+    @Test
+    public void testDoesNotCreateVirtualDisplayForSameCallback() {
+        VirtualDisplayConfig config1 = new VirtualDisplayConfig.Builder("test", /* width= */ 1,
+                /* height= */ 1, /* densityDpi= */ 1).build();
+        VirtualDisplayConfig config2 = new VirtualDisplayConfig.Builder("test2", /* width= */ 1,
+                /* height= */ 1, /* densityDpi= */ 1).build();
+        mVirtualDisplayAdapter.createVirtualDisplayLocked(mMockCallback, /* projection= */ null,
+                /* ownerUid= */ 10, /* packageName= */ "testpackage", /* surface= */ null,
+                /* flags= */ 0, config1);
+
+        DisplayDevice result = mVirtualDisplayAdapter.createVirtualDisplayLocked(mMockCallback,
+                /* projection= */ null, /* ownerUid= */ 10, /* packageName= */ "testpackage",
+                /* surface= */ null, /* flags= */ 0, config2);
+
+        assertNull(result);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 1f4563f..976e740 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -105,6 +105,7 @@
 import com.android.server.wm.ActivityTaskManagerService;
 import com.android.server.wm.WindowProcessController;
 
+import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -206,8 +207,10 @@
         setFieldValue(AppProfiler.class, profiler, "mProfilerLock", new Object());
         doReturn(new ActivityManagerService.ProcessChangeItem()).when(pr)
                 .enqueueProcessChangeItemLocked(anyInt(), anyInt());
-        sService.mOomAdjuster = new OomAdjuster(sService, sService.mProcessList,
-                new ActiveUids(sService, false));
+        sService.mOomAdjuster = sService.mConstants.ENABLE_NEW_OOMADJ
+                ? new OomAdjusterModernImpl(sService, sService.mProcessList,
+                        new ActiveUids(sService, false))
+                : new OomAdjuster(sService, sService.mProcessList, new ActiveUids(sService, false));
         sService.mOomAdjuster.mAdjSeq = 10000;
         sService.mWakefulness = new AtomicInteger(PowerManagerInternal.WAKEFULNESS_AWAKE);
         if (sService.mConstants.USE_TIERED_CACHED_ADJ) {
@@ -220,6 +223,11 @@
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
     }
 
+    @After
+    public void tearDown() {
+        sService.mOomAdjuster.resetInternal();
+    }
+
     private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
         try {
             Field field = clazz.getDeclaredField(fieldName);
@@ -249,6 +257,9 @@
         ArrayList<ProcessRecord> lru = sService.mProcessList.getLruProcessesLOSP();
         lru.clear();
         Collections.addAll(lru, apps);
+        for (ProcessRecord app : apps) {
+            sService.mOomAdjuster.onProcessBeginLocked(app);
+        }
     }
 
     /**
@@ -259,6 +270,7 @@
     @SuppressWarnings("GuardedBy")
     private void updateOomAdj(ProcessRecord... apps) {
         if (apps.length == 1) {
+            sService.mOomAdjuster.onProcessBeginLocked(apps[0]);
             sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
         } else {
             setProcessesToLru(apps);
@@ -600,10 +612,13 @@
             s.lastTopAlmostPerceptibleBindRequestUptimeMs = nowUptime;
             s.getConnections().clear();
             app.mServices.updateHasTopStartedAlmostPerceptibleServices();
+            sService.mOomAdjuster.onProcessBeginLocked(system);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
             updateOomAdj(app);
 
             assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
+
+            sService.mOomAdjuster.resetInternal();
         }
 
         // Out of grace period but valid binding allows the adjustment.
@@ -620,10 +635,13 @@
             s.lastTopAlmostPerceptibleBindRequestUptimeMs =
                     nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs;
             app.mServices.updateHasTopStartedAlmostPerceptibleServices();
+            sService.mOomAdjuster.onProcessBeginLocked(system);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
             updateOomAdj(app);
 
             assertEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
+
+            sService.mOomAdjuster.resetInternal();
         }
 
         // Out of grace period and no valid binding so no adjustment.
@@ -641,10 +659,13 @@
                     nowUptime - 2 * sService.mConstants.mServiceBindAlmostPerceptibleTimeoutMs;
             s.getConnections().clear();
             app.mServices.updateHasTopStartedAlmostPerceptibleServices();
+            sService.mOomAdjuster.onProcessBeginLocked(system);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
             updateOomAdj(app);
 
             assertNotEquals(PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ + 2, app.mState.getSetAdj());
+
+            sService.mOomAdjuster.resetInternal();
         }
     }
 
@@ -657,11 +678,12 @@
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, true));
         system.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
         system.mState.setHasTopUi(true);
+        sService.mOomAdjuster.onProcessBeginLocked(system);
         // Simulate the system starting and binding to a service in the app.
         ServiceRecord s = bindService(app, system,
                 null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+        updateOomAdj(system, app);
 
         assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
                 PERCEPTIBLE_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
@@ -850,6 +872,7 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
         ProcessRecord client = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID,
                 MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false));
+        client.mServices.setTreatLikeActivity(true);
         bindService(app, client, null, Context.BIND_WAIVE_PRIORITY
                 | Context.BIND_TREAT_LIKE_ACTIVITY, mock(IBinder.class));
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
@@ -1006,7 +1029,7 @@
         bindService(app, client, null, Context.BIND_NOT_FOREGROUND, mock(IBinder.class));
         client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        updateOomAdj(app);
+        updateOomAdj(client, app);
 
         assertEquals(PROCESS_STATE_TRANSIENT_BACKGROUND, app.mState.getSetProcState());
         assertNoBfsl(app);
@@ -1132,7 +1155,7 @@
         assertNoBfsl(app);
 
         client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
-        updateOomAdj(app);
+        updateOomAdj(client, app);
 
         assertEquals(PERSISTENT_SERVICE_ADJ, app.mState.getSetAdj());
         assertBfsl(app);
@@ -1148,7 +1171,7 @@
         bindService(app, client, null, Context.BIND_NOT_PERCEPTIBLE, mock(IBinder.class));
         client.mState.setRunningRemoteAnimation(true);
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        updateOomAdj(app);
+        updateOomAdj(client, app);
 
         assertEquals(PERCEPTIBLE_LOW_APP_ADJ, app.mState.getSetAdj());
     }
@@ -1199,6 +1222,8 @@
             updateOomAdj(client, app);
 
             assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
+
+            sService.mOomAdjuster.resetInternal();
         }
 
         {
@@ -1217,6 +1242,8 @@
             doReturn(false).when(wpc).isHeavyWeightProcess();
 
             assertEquals(PERCEPTIBLE_MEDIUM_APP_ADJ + 2, app.mState.getSetAdj());
+
+            sService.mOomAdjuster.resetInternal();
         }
 
         {
@@ -1229,9 +1256,11 @@
                     mock(IBinder.class));
             client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-            sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+            updateOomAdj(client, app);
 
             assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
+
+            sService.mOomAdjuster.resetInternal();
         }
 
         {
@@ -1246,10 +1275,12 @@
                     mock(IBinder.class));
             client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
             sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-            sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+            updateOomAdj(client, app);
             doReturn(false).when(wpc).isHeavyWeightProcess();
 
             assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
+
+            sService.mOomAdjuster.resetInternal();
         }
     }
 
@@ -1849,7 +1880,7 @@
 
         bindService(app1, client1, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class));
         bindService(app2, client2, null, Context.BIND_SCHEDULE_LIKE_TOP_APP, mock(IBinder.class));
-        updateOomAdj(app1, app2);
+        updateOomAdj(client1, client2, app1, app2);
 
         assertProcStates(app1, PROCESS_STATE_BOUND_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
                 SCHED_GROUP_TOP_APP);
@@ -1899,6 +1930,8 @@
 
         s1.getConnections().clear();
         s2.getConnections().clear();
+        client1.mServices.removeAllConnections();
+        client2.mServices.removeAllConnections();
         client1.mState.setMaxAdj(UNKNOWN_ADJ);
         client2.mState.setMaxAdj(UNKNOWN_ADJ);
         client1.mServices.setHasForegroundServices(true, 0, /* hasNoneType=*/true);
@@ -1909,7 +1942,7 @@
         bindService(app2, client2, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE,
                 mock(IBinder.class));
 
-        updateOomAdj(app1, app2);
+        updateOomAdj(client1, client2, app1, app2);
 
         // VISIBLE_APP_ADJ is the max oom-adj for BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE.
         assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, VISIBLE_APP_ADJ,
@@ -1922,7 +1955,7 @@
         doReturn(client2).when(sService).getTopApp();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
 
-        sService.mOomAdjuster.updateOomAdjLocked(app2, OOM_ADJ_REASON_NONE);
+        updateOomAdj(client2, app2);
         assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
                 SCHED_GROUP_DEFAULT);
     }
@@ -1977,6 +2010,7 @@
         app.setPendingFinishAttach(true);
         app.mState.setHasForegroundActivities(false);
 
+        sService.mOomAdjuster.onProcessBeginLocked(app);
         sService.mOomAdjuster.setAttachingProcessStatesLSP(app);
         updateOomAdj(app);
 
@@ -1991,7 +2025,9 @@
                 MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false));
         app.setPendingFinishAttach(true);
         app.mState.setHasForegroundActivities(true);
+        doReturn(app).when(sService).getTopApp();
 
+        sService.mOomAdjuster.onProcessBeginLocked(app);
         sService.mOomAdjuster.setAttachingProcessStatesLSP(app);
         updateOomAdj(app);
 
@@ -2088,7 +2124,7 @@
                     anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
             doNothing().when(sService.mServices)
                     .scheduleServiceTimeoutLocked(any(ProcessRecord.class));
-            sService.mOomAdjuster.updateOomAdjLocked(client1, OOM_ADJ_REASON_NONE);
+            updateOomAdj(client1, client2, app1, app2, app3);
 
             assertEquals(PROCESS_STATE_CACHED_EMPTY, client1.mState.getSetProcState());
             assertEquals(PROCESS_STATE_SERVICE, app1.mState.getSetProcState());
@@ -2426,6 +2462,8 @@
         lru.clear();
         lru.add(app2);
         lru.add(app);
+        sService.mOomAdjuster.onProcessBeginLocked(app2);
+        sService.mOomAdjuster.onProcessBeginLocked(app);
 
         final ComponentName cn = ComponentName.unflattenFromString(
                 MOCKAPP_PACKAGENAME + "/.TestService");
@@ -2528,7 +2566,7 @@
         doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState();
         doReturn(app).when(sService).getTopApp();
         sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
-        sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
+        updateOomAdj(app);
 
         assertEquals(FOREGROUND_APP_ADJ, app.mState.getSetAdj());
 
diff --git a/services/tests/servicestests/src/com/android/server/companion/utils/OWNERS b/services/tests/servicestests/src/com/android/server/companion/utils/OWNERS
new file mode 100644
index 0000000..008a53f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/utils/OWNERS
@@ -0,0 +1 @@
+include /services/companion/java/com/android/server/companion/OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java b/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java
new file mode 100644
index 0000000..01159b1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/utils/PackageUtilsTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.companion.utils;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.res.Resources;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.companion.PackageUtils;
+import com.android.server.pm.pkg.AndroidPackage;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+public class PackageUtilsTest {
+    private static final String[] ALLOWED_PACKAGE_NAMES = new String[]{
+            "allowed_app",
+    };
+    private static final Signature[] ALLOWED_PACKAGE_SIGNATURES = new Signature[]{
+            new Signature("001122"),
+    };
+    private static final String[] DISALLOWED_PACKAGE_NAMES = new String[]{
+            "disallowed_app",
+    };
+    private static final Signature[] DISALLOWED_PACKAGE_SIGNATURES = new Signature[]{
+            new Signature("778899"),
+    };
+
+    @Test
+    public void isAllowlisted_true() {
+        Context context = spy(
+                new ContextWrapper(
+                        InstrumentationRegistry.getInstrumentation().getTargetContext()));
+        final Resources res = spy(context.getResources());
+        doReturn(ALLOWED_PACKAGE_NAMES).when(res).getStringArray(
+                com.android.internal.R.array.config_companionDevicePackages);
+        doReturn(android.util.PackageUtils.computeSignaturesSha256Digests(
+                ALLOWED_PACKAGE_SIGNATURES)).when(res).getStringArray(
+                com.android.internal.R.array.config_companionDeviceCerts);
+        doReturn(res).when(context).getResources();
+        PackageManagerInternal pm = mock(PackageManagerInternal.class);
+        AndroidPackage ap = mock(AndroidPackage.class);
+        SigningDetails sd = new SigningDetails(
+                ALLOWED_PACKAGE_SIGNATURES,
+                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                null,
+                null);
+        doReturn(ap).when(pm).getPackage(ALLOWED_PACKAGE_NAMES[0]);
+        doReturn(sd).when(ap).getSigningDetails();
+        assertTrue(PackageUtils.isPackageAllowlisted(context, pm, ALLOWED_PACKAGE_NAMES[0]));
+    }
+
+    @Test
+    public void isAllowlisted_package_disallowed() {
+        Context context = spy(
+                new ContextWrapper(
+                        InstrumentationRegistry.getInstrumentation().getTargetContext()));
+        final Resources res = spy(context.getResources());
+        doReturn(ALLOWED_PACKAGE_NAMES).when(res).getStringArray(
+                com.android.internal.R.array.config_companionDevicePackages);
+        doReturn(android.util.PackageUtils.computeSignaturesSha256Digests(
+                ALLOWED_PACKAGE_SIGNATURES)).when(res).getStringArray(
+                com.android.internal.R.array.config_companionDeviceCerts);
+        doReturn(res).when(context).getResources();
+        PackageManagerInternal pm = mock(PackageManagerInternal.class);
+        AndroidPackage ap = mock(AndroidPackage.class);
+        SigningDetails sd = new SigningDetails(
+                ALLOWED_PACKAGE_SIGNATURES, // Giving the package a wrong signature
+                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                null,
+                null);
+        doReturn(ap).when(pm).getPackage(DISALLOWED_PACKAGE_NAMES[0]);
+        doReturn(sd).when(ap).getSigningDetails();
+        assertFalse(PackageUtils.isPackageAllowlisted(context, pm, DISALLOWED_PACKAGE_NAMES[0]));
+    }
+
+    @Test
+    public void isAllowlisted_signature_mismatch() {
+        Context context = spy(
+                new ContextWrapper(
+                        InstrumentationRegistry.getInstrumentation().getTargetContext()));
+        final Resources res = spy(context.getResources());
+        doReturn(ALLOWED_PACKAGE_NAMES).when(res).getStringArray(
+                com.android.internal.R.array.config_companionDevicePackages);
+        doReturn(android.util.PackageUtils.computeSignaturesSha256Digests(
+                ALLOWED_PACKAGE_SIGNATURES)).when(res).getStringArray(
+                com.android.internal.R.array.config_companionDeviceCerts);
+        doReturn(res).when(context).getResources();
+        PackageManagerInternal pm = mock(PackageManagerInternal.class);
+        AndroidPackage ap = mock(AndroidPackage.class);
+        SigningDetails sd = new SigningDetails(
+                DISALLOWED_PACKAGE_SIGNATURES, // Giving the package a wrong signature
+                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3,
+                null,
+                null);
+        doReturn(ap).when(pm).getPackage(ALLOWED_PACKAGE_NAMES[0]);
+        doReturn(sd).when(ap).getSigningDetails();
+        assertFalse(PackageUtils.isPackageAllowlisted(context, pm, ALLOWED_PACKAGE_NAMES[0]));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
index 9f855c5..f834cb2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
@@ -99,23 +99,45 @@
     @Test
     fun parseApplicationTag() {
         val tag = "application"
-        validateTagAttr(tag, "backupAgent",
-            R.styleable.AndroidManifestApplication_backupAgent, 1024)
-        validateTagAttrComponentName(tag, "backupAgent",
-            R.styleable.AndroidManifestApplication_backupAgent)
-        validateTagAttr(tag, "manageSpaceActivity",
-            R.styleable.AndroidManifestApplication_manageSpaceActivity, 1024)
+        validateTagAttr(
+            tag,
+            "backupAgent",
+            R.styleable.AndroidManifestApplication_backupAgent,
+            1024
+        )
+        validateTagAttrComponentName(
+            tag,
+            "backupAgent",
+            R.styleable.AndroidManifestApplication_backupAgent
+        )
+        validateTagAttr(
+            tag,
+            "manageSpaceActivity",
+            R.styleable.AndroidManifestApplication_manageSpaceActivity,
+            1024
+        )
         validateTagAttr(tag, "name", R.styleable.AndroidManifestApplication_name, 1024)
-        validateTagAttrComponentName(tag, "name",
-            R.styleable.AndroidManifestApplication_name)
+        validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestApplication_name)
         validateTagAttr(tag, "permission", R.styleable.AndroidManifestApplication_permission, 1024)
         validateTagAttr(tag, "process", R.styleable.AndroidManifestApplication_process, 1024)
-        validateTagAttr(tag, "requiredAccountType",
-            R.styleable.AndroidManifestApplication_requiredAccountType, 1024)
-        validateTagAttr(tag, "restrictedAccountType",
-            R.styleable.AndroidManifestApplication_restrictedAccountType, 1024)
-        validateTagAttr(tag, "taskAffinity",
-            R.styleable.AndroidManifestApplication_taskAffinity, 1024)
+        validateTagAttr(
+            tag,
+            "requiredAccountType",
+            R.styleable.AndroidManifestApplication_requiredAccountType,
+            1024
+        )
+        validateTagAttr(
+            tag,
+            "restrictedAccountType",
+            R.styleable.AndroidManifestApplication_restrictedAccountType,
+            1024
+        )
+        validateTagAttr(
+            tag,
+            "taskAffinity",
+            R.styleable.AndroidManifestApplication_taskAffinity,
+            1024
+        )
         validateTagCount("profileable", 100, tag)
         validateTagCount("uses-native-library", 100, tag)
         validateTagCount("receiver", 1000, tag)
@@ -159,12 +181,23 @@
     fun parseActivityAliasTag() {
         val tag = "activity-alias"
         validateTagAttr(tag, "name", R.styleable.AndroidManifestActivityAlias_name, 1024)
-        validateTagAttr(tag, "permission",
-            R.styleable.AndroidManifestActivityAlias_permission, 1024)
-        validateTagAttr(tag, "targetActivity",
-            R.styleable.AndroidManifestActivityAlias_targetActivity, 1024)
-        validateTagAttrComponentName(tag, "targetActivity",
-            R.styleable.AndroidManifestActivityAlias_targetActivity)
+        validateTagAttr(
+            tag,
+            "permission",
+            R.styleable.AndroidManifestActivityAlias_permission,
+            1024
+        )
+        validateTagAttr(
+            tag,
+            "targetActivity",
+            R.styleable.AndroidManifestActivityAlias_targetActivity,
+            1024
+        )
+        validateTagAttrComponentName(
+            tag,
+            "targetActivity",
+            R.styleable.AndroidManifestActivityAlias_targetActivity
+        )
         validateTagCount("meta-data", 1000, tag)
         validateTagCount("intent-filter", 20000, tag)
     }
@@ -173,7 +206,6 @@
     fun parseUsesLibraryTag() {
         val tag = "uses-library"
         validateTagAttr(tag, "name", R.styleable.AndroidManifestUsesLibrary_name, 1024)
-        validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestUsesLibrary_name)
     }
 
     @Test
@@ -181,10 +213,17 @@
         val tag = "activity"
         validateTagAttr(tag, "name", R.styleable.AndroidManifestActivity_name, 1024)
         validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestActivity_name)
-        validateTagAttr(tag, "parentActivityName",
-            R.styleable.AndroidManifestActivity_parentActivityName, 1024)
-        validateTagAttrComponentName(tag, "parentActivityName",
-            R.styleable.AndroidManifestActivity_parentActivityName)
+        validateTagAttr(
+            tag,
+            "parentActivityName",
+            R.styleable.AndroidManifestActivity_parentActivityName,
+            1024
+        )
+        validateTagAttrComponentName(
+            tag,
+            "parentActivityName",
+            R.styleable.AndroidManifestActivity_parentActivityName
+        )
         validateTagAttr(tag, "permission", R.styleable.AndroidManifestActivity_permission, 1024)
         validateTagAttr(tag, "process", R.styleable.AndroidManifestActivity_process, 1024)
         validateTagAttr(tag, "taskAffinity", R.styleable.AndroidManifestActivity_taskAffinity, 1024)
@@ -197,26 +236,49 @@
     fun parseOverlayTag() {
         val tag = "overlay"
         validateTagAttr(tag, "category", R.styleable.AndroidManifestResourceOverlay_category, 1024)
-        validateTagAttr(tag, "requiredSystemPropertyName",
-            R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName, 1024)
-        validateTagAttr(tag, "requiredSystemPropertyValue",
-            R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue, PROP_VALUE_MAX)
-        validateTagAttr(tag, "targetPackage",
-            R.styleable.AndroidManifestResourceOverlay_targetPackage, 256)
-        validateTagAttr(tag, "targetName",
-            R.styleable.AndroidManifestResourceOverlay_targetName, 1024)
+        validateTagAttr(
+            tag,
+            "requiredSystemPropertyName",
+            R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName,
+            1024
+        )
+        validateTagAttr(
+            tag,
+            "requiredSystemPropertyValue",
+            R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue,
+            PROP_VALUE_MAX
+        )
+        validateTagAttr(
+            tag,
+            "targetPackage",
+            R.styleable.AndroidManifestResourceOverlay_targetPackage,
+            256
+        )
+        validateTagAttr(
+            tag,
+            "targetName",
+            R.styleable.AndroidManifestResourceOverlay_targetName,
+            1024
+        )
     }
 
     @Test
     fun parseInstrumentationTag() {
         val tag = "instrumentation"
         validateTagAttr(tag, "name", R.styleable.AndroidManifestInstrumentation_name, 1024)
-        validateTagAttrComponentName(tag, "name",
-            R.styleable.AndroidManifestInstrumentation_name)
-        validateTagAttr(tag, "targetPackage",
-            R.styleable.AndroidManifestInstrumentation_targetPackage, 256)
-        validateTagAttr(tag, "targetProcesses",
-            R.styleable.AndroidManifestInstrumentation_targetProcesses, 1024)
+        validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestInstrumentation_name)
+        validateTagAttr(
+            tag,
+            "targetPackage",
+            R.styleable.AndroidManifestInstrumentation_targetPackage,
+            256
+        )
+        validateTagAttr(
+            tag,
+            "targetProcesses",
+            R.styleable.AndroidManifestInstrumentation_targetProcesses,
+            1024
+        )
     }
 
     @Test
@@ -278,10 +340,18 @@
         validateTagAttrComponentName(tag, "name", R.styleable.AndroidManifestProvider_name)
         validateTagAttr(tag, "permission", R.styleable.AndroidManifestProvider_permission, 1024)
         validateTagAttr(tag, "process", R.styleable.AndroidManifestProvider_process, 1024)
-        validateTagAttr(tag, "readPermission",
-            R.styleable.AndroidManifestProvider_readPermission, 1024)
-        validateTagAttr(tag, "writePermission",
-            R.styleable.AndroidManifestProvider_writePermission, 1024)
+        validateTagAttr(
+            tag,
+            "readPermission",
+            R.styleable.AndroidManifestProvider_readPermission,
+            1024
+        )
+        validateTagAttr(
+            tag,
+            "writePermission",
+            R.styleable.AndroidManifestProvider_writePermission,
+            1024
+        )
         validateTagCount("grant-uri-permission", 100, tag)
         validateTagCount("path-permission", 100, tag)
         validateTagCount("meta-data", 1000, tag)
@@ -292,26 +362,54 @@
     fun parseGrantUriPermissionTag() {
         val tag = "grant-uri-permission"
         validateTagAttr(tag, "path", R.styleable.AndroidManifestGrantUriPermission_path, 4000)
-        validateTagAttr(tag, "pathPrefix",
-            R.styleable.AndroidManifestGrantUriPermission_pathPrefix, 4000)
-        validateTagAttr(tag, "pathPattern",
-            R.styleable.AndroidManifestGrantUriPermission_pathPattern, 4000)
+        validateTagAttr(
+            tag,
+            "pathPrefix",
+            R.styleable.AndroidManifestGrantUriPermission_pathPrefix,
+            4000
+        )
+        validateTagAttr(
+            tag,
+            "pathPattern",
+            R.styleable.AndroidManifestGrantUriPermission_pathPattern,
+            4000
+        )
     }
 
     @Test
     fun parsePathPermissionTag() {
         val tag = "path-permission"
         validateTagAttr(tag, "path", R.styleable.AndroidManifestPathPermission_path, 4000)
-        validateTagAttr(tag, "pathPrefix",
-            R.styleable.AndroidManifestPathPermission_pathPrefix, 4000)
-        validateTagAttr(tag, "pathPattern",
-            R.styleable.AndroidManifestPathPermission_pathPattern, 4000)
-        validateTagAttr(tag, "permission",
-            R.styleable.AndroidManifestPathPermission_permission, 1024)
-        validateTagAttr(tag, "readPermission",
-            R.styleable.AndroidManifestPathPermission_readPermission, 1024)
-        validateTagAttr(tag, "writePermission",
-            R.styleable.AndroidManifestPathPermission_writePermission, 1024)
+        validateTagAttr(
+            tag,
+            "pathPrefix",
+            R.styleable.AndroidManifestPathPermission_pathPrefix,
+            4000
+        )
+        validateTagAttr(
+            tag,
+            "pathPattern",
+            R.styleable.AndroidManifestPathPermission_pathPattern,
+            4000
+        )
+        validateTagAttr(
+            tag,
+            "permission",
+            R.styleable.AndroidManifestPathPermission_permission,
+            1024
+        )
+        validateTagAttr(
+            tag,
+            "readPermission",
+            R.styleable.AndroidManifestPathPermission_readPermission,
+            1024
+        )
+        validateTagAttr(
+            tag,
+            "writePermission",
+            R.styleable.AndroidManifestPathPermission_writePermission,
+            1024
+        )
     }
 
     @Test
@@ -350,8 +448,12 @@
         validateTagAttr(tag, "pathPattern", R.styleable.AndroidManifestData_pathPattern, 4000)
         validateTagAttr(tag, "pathPrefix", R.styleable.AndroidManifestData_pathPrefix, 4000)
         validateTagAttr(tag, "pathSuffix", R.styleable.AndroidManifestData_pathSuffix, 4000)
-        validateTagAttr(tag, "pathAdvancedPattern",
-            R.styleable.AndroidManifestData_pathAdvancedPattern, 4000)
+        validateTagAttr(
+            tag,
+            "pathAdvancedPattern",
+            R.styleable.AndroidManifestData_pathAdvancedPattern,
+            4000
+        )
         validateTagAttr(tag, "mimeType", R.styleable.AndroidManifestData_mimeType, 512)
     }
 
@@ -365,8 +467,12 @@
     fun parsePermissionTag() {
         val tag = "permission"
         validateTagAttr(tag, "name", R.styleable.AndroidManifestPermission_name, 1024)
-        validateTagAttr(tag, "permissionGroup",
-            R.styleable.AndroidManifestPermission_permissionGroup, 256)
+        validateTagAttr(
+            tag,
+            "permissionGroup",
+            R.styleable.AndroidManifestPermission_permissionGroup,
+            256
+        )
     }
 
     @Test
@@ -386,14 +492,18 @@
             try {
                 validator.validateStrAttr(pullParser, attr, name)
             } catch (e: SecurityException) {
-                fail("Failed to parse attribute $attr in <$tag> as valid Java class name:" +
-                        " ${e.message}")
+                fail(
+                    "Failed to parse attribute $attr in <$tag> as valid Java class name:" +
+                        " ${e.message}"
+                )
             }
             try {
                 validator.validateResStrAttr(pullParser, index, name)
             } catch (e: SecurityException) {
-                fail("Failed to parse attribute $attr in <$tag> as valid Java class name:" +
-                        " ${e.message}")
+                fail(
+                    "Failed to parse attribute $attr in <$tag> as valid Java class name:" +
+                        " ${e.message}"
+                )
             }
         }
 
@@ -404,13 +514,17 @@
             val validator = Validator()
             pullParser.nextTag()
             validator.validate(pullParser)
-            val e1 = assertThrows("$name is not valid Java class name",
-                SecurityException::class.java) {
+            val e1 = assertThrows(
+                "$name is not valid Java class name",
+                SecurityException::class.java
+            ) {
                 validator.validateStrAttr(pullParser, attr, name)
             }
             assertEquals(expectedAttrComponentNameErrorMsg(name), e1.message)
-            val e2 = assertThrows("$name is not valid Java class name",
-                SecurityException::class.java) {
+            val e2 = assertThrows(
+                "$name is not valid Java class name",
+                SecurityException::class.java
+            ) {
                 validator.validateResStrAttr(pullParser, index, name)
             }
             assertEquals(expectedAttrComponentNameErrorMsg(name), e2.message)
@@ -437,15 +551,19 @@
         try {
             validator.validateStrAttr(pullParser, name, value)
         } catch (e: SecurityException) {
-            fail("Failed to parse valid <$tag> attribute $name with max length of $maxLen:" +
-                    " ${e.message}")
+            fail(
+                "Failed to parse valid <$tag> attribute $name with max length of $maxLen:" +
+                    " ${e.message}"
+            )
         }
         if (index != null) {
             try {
                 validator.validateResStrAttr(pullParser, index, value)
             } catch (e: SecurityException) {
-                fail("Failed to parse valid <$tag> resource string attribute $name with max" +
-                        " length of $maxLen: ${e.message}")
+                fail(
+                    "Failed to parse valid <$tag> resource string attribute $name with max" +
+                        " length of $maxLen: ${e.message}"
+                )
             }
         }
     }
@@ -485,8 +603,10 @@
         try {
             parseXmlStr(xml)
         } catch (e: SecurityException) {
-            fail("Failed to parse <$tag> with max count limit of $maxNum under" +
-                    " <$parentTag>: ${e.message}")
+            fail(
+                "Failed to parse <$tag> with max count limit of $maxNum under" +
+                        " <$parentTag>: ${e.message}"
+            )
         }
     }
 
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 54995fb..edc5df2 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -1022,9 +1022,12 @@
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100)
                 .compose();
         CombinedVibration effect = CombinedVibration.createParallel(composed);
-        long vibrationId = startThreadAndDispatcher(effect);
-
+        // We create the HalVibration here to obtain the vibration id and use it to mock the
+        // required response when calling triggerSyncedVibration.
+        HalVibration halVibration = createVibration(effect);
+        long vibrationId = halVibration.id;
         when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
+        startThreadAndDispatcher(halVibration);
 
         assertTrue(waitUntil(
                 () -> !mVibratorProviders.get(1).getEffectSegments(vibrationId).isEmpty()
@@ -1056,7 +1059,6 @@
         mVibratorProviders.get(4).setSupportedPrimitives(
                 VibrationEffect.Composition.PRIMITIVE_CLICK);
         when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
-        when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(true);
 
         VibrationEffect composed = VibrationEffect.startComposition()
                 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
@@ -1067,7 +1069,12 @@
                 .addVibrator(3, VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1))
                 .addVibrator(4, composed)
                 .combine();
-        long vibrationId = startThreadAndDispatcher(effect);
+        // We create the HalVibration here to obtain the vibration id and use it to mock the
+        // required response when calling triggerSyncedVibration.
+        HalVibration halVibration = createVibration(effect);
+        long vibrationId = halVibration.id;
+        when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(true);
+        startThreadAndDispatcher(halVibration);
         waitForCompletion();
 
         long expectedCap = IVibratorManager.CAP_SYNC
@@ -1117,13 +1124,17 @@
         mockVibrators(vibratorIds);
         mVibratorProviders.get(2).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
         when(mManagerHooks.prepareSyncedVibration(anyLong(), any())).thenReturn(true);
-        when(mManagerHooks.triggerSyncedVibration(anyLong())).thenReturn(false);
 
         CombinedVibration effect = CombinedVibration.startParallel()
                 .addVibrator(1, VibrationEffect.createOneShot(10, 100))
                 .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
                 .combine();
-        long vibrationId = startThreadAndDispatcher(effect);
+        // We create the HalVibration here to obtain the vibration id and use it to mock the
+        // required response when calling triggerSyncedVibration.
+        HalVibration halVibration = createVibration(effect);
+        long vibrationId = halVibration.id;
+        when(mManagerHooks.triggerSyncedVibration(eq(vibrationId))).thenReturn(false);
+        startThreadAndDispatcher(halVibration);
         waitForCompletion();
 
         long expectedCap = IVibratorManager.CAP_SYNC
@@ -1492,7 +1503,6 @@
         assertTrue(fakeVibrator.getAmplitudes().isEmpty());
     }
 
-    @FlakyTest
     @Test
     public void vibrate_multipleVibrations_withCancel() throws Exception {
         mVibratorProviders.get(VIBRATOR_ID).setSupportedEffects(
diff --git a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
index 1c6408b..56c3ec0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AssistDataRequesterTest.java
@@ -50,7 +50,6 @@
 import android.util.Log;
 import android.view.IWindowManager;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 
 import com.android.server.am.AssistDataRequester;
@@ -154,7 +153,6 @@
                 .noteOpNoThrow(eq(OP_ASSIST_SCREENSHOT), anyInt(), anyString(), any(), any());
     }
 
-    @FlakyTest(bugId = 280107567)
     @Test
     public void testRequestData() throws Exception {
         setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
@@ -262,7 +260,6 @@
         assertReceivedDataCount(0, 1, 0, 1);
     }
 
-    @FlakyTest(bugId = 280107567)
     @Test
     public void testNoFetchScreenshots_expectNoScreenshotCallbacks() throws Exception {
         setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
@@ -274,7 +271,6 @@
         assertReceivedDataCount(5, 5, 0, 0);
     }
 
-    @FlakyTest(bugId = 280107567)
     @Test
     public void testDisallowAssistScreenshot_expectNullScreenshotCallback() throws Exception {
         setupMocks(CURRENT_ACTIVITY_ASSIST_ALLOWED, CALLER_ASSIST_STRUCTURE_ALLOWED,
@@ -321,7 +317,8 @@
         assertEquals("Expected " + numPendingScreenshots + " pending screenshots, got "
                         + mDataRequester.getPendingScreenshotCount(),
                 numPendingScreenshots, mDataRequester.getPendingScreenshotCount());
-        assertFalse("Expected request NOT completed", mCallbacks.mRequestCompleted);
+        assertEquals("Expected request NOT completed, unless no pending data",
+                numPendingData == 0 && numPendingScreenshots == 0, mCallbacks.mRequestCompleted);
         mGate.countDown();
         waitForIdle(mHandler);
         assertEquals("Expected " + numReceivedData + " data, received "
@@ -380,14 +377,7 @@
 
         @Override
         public void onAssistRequestCompleted() {
-            mHandler.post(() -> {
-                try {
-                    mGate.await(10, TimeUnit.SECONDS);
-                    mRequestCompleted = true;
-                } catch (InterruptedException e) {
-                    Log.e(TAG, "Failed to wait", e);
-                }
-            });
+            mRequestCompleted = true;
         }
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
index 8bc4ced..db08eab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
@@ -34,6 +34,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.InputChannel;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
@@ -72,6 +73,7 @@
         doReturn(mock(InputMonitor.class)).when(mDisplayContent).getInputMonitor();
     }
 
+    @FlakyTest(bugId = 291067614)
     @Test
     public void testStartAndFinishPositioning() {
         assertFalse(mTarget.isPositioningLocked());
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index be87766..3d83caf2 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -21,6 +21,7 @@
 #include <string>
 #include <vector>
 
+#include "android-base/parseint.h"
 #include "android-base/stringprintf.h"
 #include "android-base/strings.h"
 #include "androidfw/BigBuffer.h"
@@ -229,14 +230,29 @@
   static const char* const sMinorVersion = "19";
 
   // The build id of aapt2 binary.
-  static std::string sBuildId = android::build::GetBuildNumber();
+  static const std::string sBuildId = [] {
+    std::string buildNumber = android::build::GetBuildNumber();
 
-  if (android::base::StartsWith(sBuildId, "eng.")) {
-    time_t now = time(0);
-    tm* ltm = localtime(&now);
+    if (android::base::StartsWith(buildNumber, "eng.")) {
+      // android::build::GetBuildNumber() returns something like "eng.user.20230725.214219" where
+      // the latter two parts are "yyyyMMdd.HHmmss" at build time. Use "yyyyMM" in the fingerprint.
+      std::vector<std::string> parts = util::Split(buildNumber, '.');
+      int buildYear;
+      int buildMonth;
+      if (parts.size() < 3 || parts[2].length() < 6 ||
+          !android::base::ParseInt(parts[2].substr(0, 4), &buildYear) ||
+          !android::base::ParseInt(parts[2].substr(4, 2), &buildMonth)) {
+        // Fallback to localtime() if GetBuildNumber() returns an unexpected output.
+        time_t now = time(0);
+        tm* ltm = localtime(&now);
+        buildYear = 1900 + ltm->tm_year;
+        buildMonth = 1 + ltm->tm_mon;
+      }
 
-    sBuildId = android::base::StringPrintf("eng.%d%d", 1900 + ltm->tm_year, 1 + ltm->tm_mon);
-  }
+      buildNumber = android::base::StringPrintf("eng.%04d%02d", buildYear, buildMonth);
+    }
+    return buildNumber;
+  }();
 
   return android::base::StringPrintf("%s.%s-%s", sMajorVersion, sMinorVersion, sBuildId.c_str());
 }