Merge "Fix the way caller is verified to support different profiles (personal, working, etc.) during app (un-)archiving" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 7f261d4..1b5c953 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12606,6 +12606,7 @@
     method public boolean isStagedSessionApplied();
     method public boolean isStagedSessionFailed();
     method public boolean isStagedSessionReady();
+    method @FlaggedApi("android.content.pm.archiving") public boolean isUnarchival();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.SessionInfo> CREATOR;
     field public static final int INVALID_ID = -1; // 0xffffffff
@@ -13296,6 +13297,7 @@
     method @NonNull public java.util.List<android.content.pm.VersionedPackage> getDependentPackages();
     method @IntRange(from=0xffffffff) public long getLongVersion();
     method public String getName();
+    method @FlaggedApi("android.content.pm.sdk_lib_independence") @NonNull public java.util.List<android.content.pm.VersionedPackage> getOptionalDependentPackages();
     method public int getType();
     method @Deprecated @IntRange(from=0xffffffff) public int getVersion();
     method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index e7803fb..d395b8c 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -136,6 +136,7 @@
 package android.content.pm {
 
   public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
+    method @FlaggedApi("android.content.pm.sdk_lib_independence") @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getOptionalSharedLibraryInfos();
     method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraryInfos();
     field public static final int HIDDEN_API_ENFORCEMENT_DEFAULT = -1; // 0xffffffff
     field public static final int HIDDEN_API_ENFORCEMENT_DISABLED = 0; // 0x0
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 87c86df..287d2bd 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2909,7 +2909,7 @@
         try {
             return mPM.setPackagesSuspendedAsUser(packageNames, suspended, appExtras,
                     launcherExtras, dialogInfo, flags, mContext.getOpPackageName(),
-                    getUserId());
+                    UserHandle.myUserId() /* suspendingUserId */, getUserId() /* targetUserId */);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 16a80e9..3713380 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -19,6 +19,7 @@
 import static android.os.Build.VERSION_CODES.DONUT;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1065,11 +1066,25 @@
      * PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving
      * the structure.
      *
+     * NOTE: the list also contains the result of {@link #getOptionalSharedLibraryInfos}.
+     *
      * {@hide}
      */
+    @Nullable
     public List<SharedLibraryInfo> sharedLibraryInfos;
 
     /**
+     * List of all shared libraries this application is optionally linked against.
+     * This field is only set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES
+     * PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving
+     * the structure.
+     *
+     * @hide
+     */
+    @Nullable
+    public List<SharedLibraryInfo> optionalSharedLibraryInfos;
+
+    /**
      * Full path to the default directory assigned to the package for its
      * persistent data.
      */
@@ -1937,6 +1952,7 @@
         seInfoUser = orig.seInfoUser;
         sharedLibraryFiles = orig.sharedLibraryFiles;
         sharedLibraryInfos = orig.sharedLibraryInfos;
+        optionalSharedLibraryInfos = orig.optionalSharedLibraryInfos;
         dataDir = orig.dataDir;
         deviceProtectedDataDir = orig.deviceProtectedDataDir;
         credentialProtectedDataDir = orig.credentialProtectedDataDir;
@@ -2029,6 +2045,7 @@
         dest.writeString8(seInfoUser);
         dest.writeString8Array(sharedLibraryFiles);
         dest.writeTypedList(sharedLibraryInfos);
+        dest.writeTypedList(optionalSharedLibraryInfos);
         dest.writeString8(dataDir);
         dest.writeString8(deviceProtectedDataDir);
         dest.writeString8(credentialProtectedDataDir);
@@ -2129,6 +2146,7 @@
         seInfoUser = source.readString8();
         sharedLibraryFiles = source.createString8Array();
         sharedLibraryInfos = source.createTypedArrayList(SharedLibraryInfo.CREATOR);
+        optionalSharedLibraryInfos = source.createTypedArrayList(SharedLibraryInfo.CREATOR);
         dataDir = source.readString8();
         deviceProtectedDataDir = source.readString8();
         credentialProtectedDataDir = source.readString8();
@@ -2760,6 +2778,8 @@
      *  list will only be set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES
      *  PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving the structure.
      *
+     *  NOTE: the list also contains the result of {@link #getOptionalSharedLibraryInfos}.
+     *
      * @hide
      */
     @NonNull
@@ -2772,6 +2792,23 @@
     }
 
     /**
+     *  List of all shared libraries this application is optionally linked against. This
+     *  list will only be set if the {@link PackageManager#GET_SHARED_LIBRARY_FILES
+     *  PackageManager.GET_SHARED_LIBRARY_FILES} flag was used when retrieving the structure.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @FlaggedApi(Flags.FLAG_SDK_LIB_INDEPENDENCE)
+    public List<SharedLibraryInfo> getOptionalSharedLibraryInfos() {
+        if (optionalSharedLibraryInfos == null) {
+            return Collections.EMPTY_LIST;
+        }
+        return optionalSharedLibraryInfos;
+    }
+
+    /**
      * Gets the trusted host certificate digests of apps that are allowed to embed activities of
      * this application. The digests are computed using the SHA-256 digest algorithm.
      * @see android.R.attr#knownActivityEmbeddingCerts
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 6f7299a..a97de63 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -65,6 +65,8 @@
             in UserHandle user);
     LauncherUserInfo getLauncherUserInfo(in UserHandle user);
     List<String> getPreInstalledSystemPackages(in UserHandle user);
+    IntentSender getAppMarketActivityIntent(String callingPackage, String packageName,
+            in UserHandle user);
     void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage,
             String callingFeatureId, in ComponentName component, in Rect sourceBounds,
             in Bundle opts, in UserHandle user);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 98623de..6dc8d47 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -300,7 +300,8 @@
 
     String[] setPackagesSuspendedAsUser(in String[] packageNames, boolean suspended,
             in PersistableBundle appExtras, in PersistableBundle launcherExtras,
-            in SuspendDialogInfo dialogInfo, int flags, String callingPackage, int userId);
+            in SuspendDialogInfo dialogInfo, int flags, String suspendingPackage,
+            int suspendingUserId, int targetUserId);
 
     String[] getUnsuspendablePackagesForUser(in String[] packageNames, int userId);
 
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index ccc8f09..1d2b1af 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -799,6 +799,55 @@
         }
     }
 
+
+    /**
+     * Returns an intent sender which can be used to start the App Market activity (Installer
+     * Activity).
+     * This method is primarily used to get an intent sender which starts App Market activity for
+     * another profile, if the caller is not otherwise allowed to start activity in that profile.
+     *
+     * <p>When packageName is set, intent sender to start the App Market Activity which installed
+     * the package in calling user will be returned, but for the profile passed.
+     *
+     * <p>When packageName is not set, intent sender to launch the default App Market Activity for
+     * the profile will be returned. In case there are multiple App Market Activities available for
+     * the profile, IntentPicker will be started, allowing user to choose the preferred activity.
+     *
+     * <p>The method will fall back to the behaviour of not having the packageName set, in case:
+     * <ul>
+     *     <li>No activity for the packageName is found in calling user-space.</li>
+     *     <li>The App Market Activity which installed the package in calling user-space is not
+     *         present.</li>
+     *     <li>The App Market Activity which installed the package in calling user-space is not
+     *         present in the profile passed.</li>
+     * </ul>
+     * </p>
+     *
+     *
+     *
+     * @param packageName the package for which intent sender to launch App Market Activity is
+     *                    required.
+     * @param user the profile for which intent sender to launch App Market Activity is required.
+     * @return {@link IntentSender} object which launches the App Market Activity, null in case
+     *         there is no such activity.
+     * @hide
+     */
+    @Nullable
+    @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+    public IntentSender getAppMarketActivityIntent(@Nullable String packageName,
+            @NonNull UserHandle user) {
+        if (DEBUG) {
+            Log.i(TAG, "getAppMarketActivityIntent for package: " + packageName
+                    + " user: " + user);
+        }
+        try {
+            return mService.getAppMarketActivityIntent(mContext.getPackageName(),
+                    packageName, user);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Returns the list of the system packages that are installed at user creation.
      *
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index e395127..0e131b4 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -4381,6 +4381,17 @@
             return pendingUserActionReason;
         }
 
+        /**
+         * Returns true if the session is an unarchival.
+         *
+         * @see PackageInstaller#requestUnarchive
+         */
+        @FlaggedApi(Flags.FLAG_ARCHIVING)
+        public boolean isUnarchival() {
+            return (installFlags & PackageManager.INSTALL_UNARCHIVE) != 0;
+        }
+
+
         @Override
         public int describeContents() {
             return 0;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index a5d16f2..a863870 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1517,6 +1517,7 @@
             INSTALL_REQUEST_UPDATE_OWNERSHIP,
             INSTALL_IGNORE_DEXOPT_PROFILE,
             INSTALL_UNARCHIVE_DRAFT,
+            INSTALL_UNARCHIVE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface InstallFlags {}
@@ -1764,13 +1765,22 @@
      * If set, then the session is a draft session created for an upcoming unarchival by its
      * installer.
      *
-     * @see PackageInstaller#requestUnarchive(String)
+     * @see PackageInstaller#requestUnarchive
      *
      * @hide
      */
     public static final int INSTALL_UNARCHIVE_DRAFT = 1 << 29;
 
     /**
+     * If set, then the {@link PackageInstaller.Session} is an unarchival.
+     *
+     * @see PackageInstaller#requestUnarchive
+     *
+     * @hide
+     */
+    public static final int INSTALL_UNARCHIVE = 1 << 30;
+
+    /**
      * Flag parameter for {@link #installPackage} to force a non-staged update of an APEX. This is
      * a development-only feature and should not be used on end user devices.
      *
@@ -9990,6 +10000,9 @@
      * device administrators or apps holding {@link android.Manifest.permission#MANAGE_USERS} or
      * {@link android.Manifest.permission#SUSPEND_APPS}.
      *
+     * <p>
+     * <strong>Note:</strong>This API doesn't support cross user suspension and should only be used
+     * for testing.
      * @param suspendedPackage The package that has been suspended.
      * @return Name of the package that suspended the given package. Returns {@code null} if the
      * given package is not currently suspended and the platform package name - i.e.
diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java
index 25ba725..5acebf5 100644
--- a/core/java/android/content/pm/SharedLibraryInfo.java
+++ b/core/java/android/content/pm/SharedLibraryInfo.java
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -23,6 +24,7 @@
 import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Pair;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -99,20 +101,22 @@
     private final boolean mIsNative;
     private final VersionedPackage mDeclaringPackage;
     private final List<VersionedPackage> mDependentPackages;
+
+    private final List<VersionedPackage> mOptionalDependentPackages;
     private List<SharedLibraryInfo> mDependencies;
 
     /**
      * Creates a new instance.
      *
-     * @param codePaths For a non {@link #TYPE_BUILTIN builtin} library, the locations of jars of
-     *                  this shared library. Null for builtin library.
-     * @param name The lib name.
-     * @param version The lib version if not builtin.
-     * @param type The lib type.
-     * @param declaringPackage The package that declares the library.
+     * @param codePaths         For a non {@link #TYPE_BUILTIN builtin} library, the locations of
+     *                          jars of
+     *                          this shared library. Null for builtin library.
+     * @param name              The lib name.
+     * @param version           The lib version if not builtin.
+     * @param type              The lib type.
+     * @param declaringPackage  The package that declares the library.
      * @param dependentPackages The packages that depend on the library.
-     * @param isNative indicate if this shared lib is a native lib or not (i.e. java)
-     *
+     * @param isNative          indicate if this shared lib is a native lib or not (i.e. java)
      * @hide
      */
     public SharedLibraryInfo(String path, String packageName, List<String> codePaths,
@@ -129,6 +133,58 @@
         mDependentPackages = dependentPackages;
         mDependencies = dependencies;
         mIsNative = isNative;
+        mOptionalDependentPackages = null;
+    }
+
+    /**
+     * Creates a new instance.
+     *
+     * @param codePaths For a non {@link #TYPE_BUILTIN builtin} library, the locations of jars of
+     *                  this shared library. Null for builtin library.
+     * @param name The lib name.
+     * @param version The lib version if not builtin.
+     * @param type The lib type.
+     * @param declaringPackage The package that declares the library.
+     * @param isNative indicate if this shared lib is a native lib or not (i.e. java)
+     * @param allDependentPackages All packages that depend on the library (including the optional
+     *                             sdk libraries).
+     *
+     * @hide
+     */
+    public SharedLibraryInfo(String path, String packageName, List<String> codePaths,
+            String name, long version, int type,
+            VersionedPackage declaringPackage,
+            List<SharedLibraryInfo> dependencies, boolean isNative,
+            Pair<List<VersionedPackage>, List<Boolean>> allDependentPackages) {
+        mPath = path;
+        mPackageName = packageName;
+        mCodePaths = codePaths;
+        mName = name;
+        mVersion = version;
+        mType = type;
+        mDeclaringPackage = declaringPackage;
+        mDependencies = dependencies;
+        mIsNative = isNative;
+
+        var allDependents = allDependentPackages.first;
+        var usesLibOptional = allDependentPackages.second;
+        mDependentPackages = allDependents;
+        List<VersionedPackage> optionalDependents = null;
+        if (mType == SharedLibraryInfo.TYPE_SDK_PACKAGE
+                && Flags.sdkLibIndependence() && allDependents != null
+                && usesLibOptional != null
+                && allDependents.size() == usesLibOptional.size()) {
+            for (int k = 0; k < allDependents.size(); k++) {
+                VersionedPackage versionedPackage = allDependents.get(k);
+                if (usesLibOptional.get(k)) {
+                    if (optionalDependents == null) {
+                        optionalDependents = new ArrayList<>();
+                    }
+                    optionalDependents.add(versionedPackage);
+                }
+            }
+        }
+        mOptionalDependentPackages = optionalDependents;
     }
 
     private SharedLibraryInfo(Parcel parcel) {
@@ -148,6 +204,8 @@
                 parcel.readArrayList(null, android.content.pm.VersionedPackage.class);
         mDependencies = parcel.createTypedArrayList(SharedLibraryInfo.CREATOR);
         mIsNative = parcel.readBoolean();
+        mOptionalDependentPackages = parcel.readParcelableList(new ArrayList<>(),
+                VersionedPackage.class.getClassLoader(), VersionedPackage.class);
     }
 
     /**
@@ -324,6 +382,8 @@
     /**
      * Gets the packages that depend on the library.
      *
+     * NOTE: the list also contains the result of {@link #getOptionalDependentPackages}.
+     *
      * @return The dependent packages.
      */
     public @NonNull List<VersionedPackage> getDependentPackages() {
@@ -333,6 +393,19 @@
         return mDependentPackages;
     }
 
+    /**
+     * Gets the packages that optionally depend on the library.
+     *
+     * @return The dependent packages.
+     */
+    @FlaggedApi(Flags.FLAG_SDK_LIB_INDEPENDENCE)
+    public @NonNull List<VersionedPackage> getOptionalDependentPackages() {
+        if (mOptionalDependentPackages == null) {
+            return Collections.emptyList();
+        }
+        return mOptionalDependentPackages;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -362,6 +435,7 @@
         parcel.writeList(mDependentPackages);
         parcel.writeTypedList(mDependencies);
         parcel.writeBoolean(mIsNative);
+        parcel.writeParcelableList(mOptionalDependentPackages, flags);
     }
 
     private static String typeToString(int type) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index c7e1807..e3d9c60 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -592,6 +592,13 @@
     int TRANSIT_FLAG_KEYGUARD_UNOCCLUDING = (1 << 13); // 0x2000
 
     /**
+     * Transition flag: Indicates that there is a physical display switch
+     * TODO(b/316112906) remove after defer_display_updates flag roll out
+     * @hide
+     */
+    int TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH = (1 << 14); // 0x4000
+
+    /**
      * @hide
      */
     @IntDef(flag = true, prefix = { "TRANSIT_FLAG_" }, value = {
@@ -608,7 +615,8 @@
             TRANSIT_FLAG_INVISIBLE,
             TRANSIT_FLAG_KEYGUARD_APPEARING,
             TRANSIT_FLAG_KEYGUARD_OCCLUDING,
-            TRANSIT_FLAG_KEYGUARD_UNOCCLUDING
+            TRANSIT_FLAG_KEYGUARD_UNOCCLUDING,
+            TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface TransitionFlags {}
diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java
index efc1455..f1aa330 100644
--- a/core/java/com/android/internal/app/SuspendedAppActivity.java
+++ b/core/java/com/android/internal/app/SuspendedAppActivity.java
@@ -39,6 +39,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.SuspendDialogInfo;
+import android.content.pm.UserPackage;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
@@ -308,7 +309,8 @@
                         try {
                             final String[] errored = ipm.setPackagesSuspendedAsUser(
                                     new String[]{mSuspendedPackage}, false, null, null, null, 0,
-                                    mSuspendingPackage, mUserId);
+                                    mSuspendingPackage, mUserId /* suspendingUserId */,
+                                    mUserId /* targetUserId */);
                             if (ArrayUtils.contains(errored, mSuspendedPackage)) {
                                 Slog.e(TAG, "Could not unsuspend " + mSuspendedPackage);
                                 break;
@@ -350,17 +352,18 @@
     }
 
     public static Intent createSuspendedAppInterceptIntent(String suspendedPackage,
-            String suspendingPackage, SuspendDialogInfo dialogInfo, Bundle options,
+            UserPackage suspendingPackage, SuspendDialogInfo dialogInfo, Bundle options,
             IntentSender onUnsuspend, int userId) {
-        return new Intent()
+        Intent intent = new Intent()
                 .setClassName("android", SuspendedAppActivity.class.getName())
                 .putExtra(EXTRA_SUSPENDED_PACKAGE, suspendedPackage)
                 .putExtra(EXTRA_DIALOG_INFO, dialogInfo)
-                .putExtra(EXTRA_SUSPENDING_PACKAGE, suspendingPackage)
+                .putExtra(EXTRA_SUSPENDING_PACKAGE, suspendingPackage.packageName)
                 .putExtra(EXTRA_UNSUSPEND_INTENT, onUnsuspend)
                 .putExtra(EXTRA_ACTIVITY_OPTIONS, options)
                 .putExtra(Intent.EXTRA_USER_ID, userId)
                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                         | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        return intent;
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 20ff79f..98d343b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -18,6 +18,7 @@
 
 import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH;
 
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;
 
@@ -245,23 +246,29 @@
     public boolean shouldPlayUnfoldAnimation(@NonNull TransitionInfo transitionInfo) {
         // Unfold animation won't play when animations are disabled
         if (!ValueAnimator.areAnimatorsEnabled()) return false;
+        // Only handle transitions that are marked as physical display switch
+        // See PhysicalDisplaySwitchTransitionLauncher for the conditions
+        if ((transitionInfo.getFlags() & TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH) == 0) return false;
 
         for (int i = 0; i < transitionInfo.getChanges().size(); i++) {
             final TransitionInfo.Change change = transitionInfo.getChanges().get(i);
-            if ((change.getFlags() & TransitionInfo.FLAG_IS_DISPLAY) != 0) {
-                if (change.getEndAbsBounds() == null || change.getStartAbsBounds() == null) {
-                    continue;
-                }
+            // We are interested only in display container changes
+            if ((change.getFlags() & TransitionInfo.FLAG_IS_DISPLAY) == 0) {
+                continue;
+            }
 
-                // Handle only unfolding, currently we don't have an animation when folding
-                final int afterArea =
-                        change.getEndAbsBounds().width() * change.getEndAbsBounds().height();
-                final int beforeArea = change.getStartAbsBounds().width()
-                        * change.getStartAbsBounds().height();
+            // Handle only unfolding, currently we don't have an animation when folding
+            if (change.getEndAbsBounds() == null || change.getStartAbsBounds() == null) {
+                continue;
+            }
 
-                if (afterArea > beforeArea) {
-                    return true;
-                }
+            final int afterArea =
+                    change.getEndAbsBounds().width() * change.getEndAbsBounds().height();
+            final int beforeArea = change.getStartAbsBounds().width()
+                    * change.getStartAbsBounds().height();
+
+            if (afterArea > beforeArea) {
+                return true;
             }
         }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
index fc1fe1c..c5e229f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -18,12 +18,11 @@
 
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH;
 import static android.view.WindowManager.TRANSIT_NONE;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
@@ -40,22 +39,17 @@
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
 
-import com.android.window.flags.FakeFeatureFlagsImpl;
-import com.android.window.flags.FeatureFlags;
-import com.android.window.flags.Flags;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.transition.TransitionInfoBuilder;
+import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
 import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
 import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
 
 import org.junit.Before;
 import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -196,6 +190,22 @@
     }
 
     @Test
+    public void startAnimation_differentTransitionFromRequestWithResize_doesNotStartAnimation() {
+        mUnfoldTransitionHandler.handleRequest(new Binder(), createNoneTransitionInfo());
+        TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
+
+        boolean animationStarted = mUnfoldTransitionHandler.startAnimation(
+                mTransition,
+                createDisplayResizeTransitionInfo(),
+                mock(SurfaceControl.Transaction.class),
+                mock(SurfaceControl.Transaction.class),
+                finishCallback
+        );
+
+        assertThat(animationStarted).isFalse();
+    }
+
+    @Test
     public void startAnimation_differentTransitionFromRequestWithoutUnfold_doesNotStart() {
         mUnfoldTransitionHandler.handleRequest(new Binder(), createNoneTransitionInfo());
         TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class);
@@ -403,24 +413,18 @@
         }
     }
 
-    static class TestCase {
-        private final boolean mShouldHandleMixedUnfold;
-
-        public TestCase(boolean shouldHandleMixedUnfold) {
-            mShouldHandleMixedUnfold = shouldHandleMixedUnfold;
-        }
-
-        public boolean mixedUnfoldFlagEnabled() {
-            return mShouldHandleMixedUnfold;
-        }
-
-        @Override
-        public String toString() {
-            return "shouldHandleMixedUnfold flag = " + mShouldHandleMixedUnfold;
-        }
+    private TransitionInfo createUnfoldTransitionInfo() {
+        TransitionInfo transitionInfo = new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0);
+        TransitionInfo.Change change = new TransitionInfo.Change(null, mock(SurfaceControl.class));
+        change.setStartAbsBounds(new Rect(0, 0, 10, 10));
+        change.setEndAbsBounds(new Rect(0, 0, 100, 100));
+        change.setFlags(TransitionInfo.FLAG_IS_DISPLAY);
+        transitionInfo.addChange(change);
+        transitionInfo.setFlags(TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH);
+        return transitionInfo;
     }
 
-    private TransitionInfo createUnfoldTransitionInfo() {
+    private TransitionInfo createDisplayResizeTransitionInfo() {
         TransitionInfo transitionInfo = new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0);
         TransitionInfo.Change change = new TransitionInfo.Change(null, mock(SurfaceControl.class));
         change.setStartAbsBounds(new Rect(0, 0, 10, 10));
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a26b311..facb244 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -61,6 +61,13 @@
 }
 
 flag {
+    name: "refactor_get_current_user"
+    namespace: "systemui"
+    description: "KeyguardUpdateMonitor.getCurrentUser() was providing outdated results."
+    bug: "305984787"
+}
+
+flag {
     name: "notification_throttle_hun"
     namespace: "systemui"
     description: "During notification avalanche, throttle HUNs showing in fast succession."
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index e538e09..2944bd9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -174,8 +174,8 @@
     lerp: (T, T, Float) -> T,
     canOverflow: Boolean,
 ): T {
-    val state = layoutImpl.state.transitionState
-    if (state !is TransitionState.Transition || !layoutImpl.isTransitionReady(state)) {
+    val transition = layoutImpl.state.currentTransition
+    if (transition == null || !layoutImpl.isTransitionReady(transition)) {
         return sharedValue.value
     }
 
@@ -191,10 +191,11 @@
         return value as Element.SharedValue<T>
     }
 
-    val fromValue = sceneValue(state.fromScene)
-    val toValue = sceneValue(state.toScene)
+    val fromValue = sceneValue(transition.fromScene)
+    val toValue = sceneValue(transition.toScene)
     return if (fromValue != null && toValue != null) {
-        val progress = if (canOverflow) state.progress else state.progress.coerceIn(0f, 1f)
+        val progress =
+            if (canOverflow) transition.progress else transition.progress.coerceIn(0f, 1f)
         lerp(fromValue.value, toValue.value, progress)
     } else if (fromValue != null) {
         fromValue.value
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index de69c37..ba6d00e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -28,11 +28,11 @@
  * the currently running transition, if there is one.
  */
 internal fun CoroutineScope.animateToScene(
-    layoutImpl: SceneTransitionLayoutImpl,
+    layoutState: SceneTransitionLayoutStateImpl,
     target: SceneKey,
 ) {
-    val state = layoutImpl.state.transitionState
-    if (state.currentScene == target) {
+    val transitionState = layoutState.transitionState
+    if (transitionState.currentScene == target) {
         // This can happen in 3 different situations, for which there isn't anything else to do:
         //  1. There is no ongoing transition and [target] is already the current scene.
         //  2. The user is swiping to [target] from another scene and released their pointer such
@@ -44,44 +44,47 @@
         return
     }
 
-    when (state) {
-        is TransitionState.Idle -> animate(layoutImpl, target)
+    when (transitionState) {
+        is TransitionState.Idle -> animate(layoutState, target)
         is TransitionState.Transition -> {
             // A transition is currently running: first check whether `transition.toScene` or
             // `transition.fromScene` is the same as our target scene, in which case the transition
             // can be accelerated or reversed to end up in the target state.
 
-            if (state.toScene == target) {
+            if (transitionState.toScene == target) {
                 // The user is currently swiping to [target] but didn't release their pointer yet:
                 // animate the progress to `1`.
 
-                check(state.fromScene == state.currentScene)
-                val progress = state.progress
+                check(transitionState.fromScene == transitionState.currentScene)
+                val progress = transitionState.progress
                 if ((1f - progress).absoluteValue < ProgressVisibilityThreshold) {
-                    // The transition is already finished (progress ~= 1): no need to animate.
-                    layoutImpl.state.transitionState = TransitionState.Idle(state.currentScene)
+                    // The transition is already finished (progress ~= 1): no need to animate. We
+                    // finish the current transition early to make sure that the current state
+                    // change is committed.
+                    layoutState.finishTransition(transitionState, transitionState.currentScene)
                 } else {
                     // The transition is in progress: start the canned animation at the same
                     // progress as it was in.
                     // TODO(b/290184746): Also take the current velocity into account.
-                    animate(layoutImpl, target, startProgress = progress)
+                    animate(layoutState, target, startProgress = progress)
                 }
 
                 return
             }
 
-            if (state.fromScene == target) {
+            if (transitionState.fromScene == target) {
                 // There is a transition from [target] to another scene: simply animate the same
                 // transition progress to `0`.
 
-                check(state.toScene == state.currentScene)
-                val progress = state.progress
+                check(transitionState.toScene == transitionState.currentScene)
+                val progress = transitionState.progress
                 if (progress.absoluteValue < ProgressVisibilityThreshold) {
-                    // The transition is at progress ~= 0: no need to animate.
-                    layoutImpl.state.transitionState = TransitionState.Idle(state.currentScene)
+                    // The transition is at progress ~= 0: no need to animate.We finish the current
+                    // transition early to make sure that the current state change is committed.
+                    layoutState.finishTransition(transitionState, transitionState.currentScene)
                 } else {
                     // TODO(b/290184746): Also take the current velocity into account.
-                    animate(layoutImpl, target, startProgress = progress, reversed = true)
+                    animate(layoutState, target, startProgress = progress, reversed = true)
                 }
 
                 return
@@ -89,27 +92,22 @@
 
             // Generic interruption; the current transition is neither from or to [target].
             // TODO(b/290930950): Better handle interruptions here.
-            animate(layoutImpl, target)
+            animate(layoutState, target)
         }
     }
 }
 
 private fun CoroutineScope.animate(
-    layoutImpl: SceneTransitionLayoutImpl,
+    layoutState: SceneTransitionLayoutStateImpl,
     target: SceneKey,
     startProgress: Float = 0f,
     reversed: Boolean = false,
 ) {
-    val fromScene = layoutImpl.state.transitionState.currentScene
+    val fromScene = layoutState.transitionState.currentScene
     val isUserInput =
-        (layoutImpl.state.transitionState as? TransitionState.Transition)?.isInitiatedByUserInput
+        (layoutState.transitionState as? TransitionState.Transition)?.isInitiatedByUserInput
             ?: false
 
-    val animationSpec = layoutImpl.transitions.transitionSpec(fromScene, target).spec
-    val visibilityThreshold =
-        (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
-    val animatable = Animatable(startProgress, visibilityThreshold = visibilityThreshold)
-
     val targetProgress = if (reversed) 0f else 1f
     val transition =
         if (reversed) {
@@ -119,7 +117,6 @@
                 currentScene = target,
                 isInitiatedByUserInput = isUserInput,
                 isUserInputOngoing = false,
-                animatable = animatable,
             )
         } else {
             OneOffTransition(
@@ -128,21 +125,27 @@
                 currentScene = target,
                 isInitiatedByUserInput = isUserInput,
                 isUserInputOngoing = false,
-                animatable = animatable,
             )
         }
 
-    // Change the current layout state to use this new transition.
-    layoutImpl.state.transitionState = transition
+    // Change the current layout state to start this new transition. This will compute the
+    // TransformationSpec associated to this transition, which we need to initialize the Animatable
+    // that will actually animate it.
+    layoutState.startTransition(transition)
+
+    // The transformation now contains the spec that we should use to instantiate the Animatable.
+    val animationSpec = layoutState.transformationSpec.progressSpec
+    val visibilityThreshold =
+        (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
+    val animatable =
+        Animatable(startProgress, visibilityThreshold = visibilityThreshold).also {
+            transition.animatable = it
+        }
 
     // Animate the progress to its target value.
     launch {
         animatable.animateTo(targetProgress, animationSpec)
-
-        // Unless some other external state change happened, the state should now be idle.
-        if (layoutImpl.state.transitionState == transition) {
-            layoutImpl.state.transitionState = TransitionState.Idle(target)
-        }
+        layoutState.finishTransition(transition, target)
     }
 }
 
@@ -152,8 +155,16 @@
     override val currentScene: SceneKey,
     override val isInitiatedByUserInput: Boolean,
     override val isUserInputOngoing: Boolean,
-    private val animatable: Animatable<Float, AnimationVector1D>,
 ) : TransitionState.Transition(fromScene, toScene) {
+    /**
+     * The animatable used to animate this transition.
+     *
+     * Note: This is lateinit because we need to first create this Transition object so that
+     * [SceneTransitionLayoutState] can compute the transformations and animation spec associated to
+     * it, which is need to initialize this Animatable.
+     */
+    lateinit var animatable: Animatable<Float, AnimationVector1D>
+
     override val progress: Float
         get() = animatable.value
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 431a8ae..5dc1079 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -181,15 +181,11 @@
 }
 
 internal class ElementNode(
-    layoutImpl: SceneTransitionLayoutImpl,
-    scene: Scene,
-    element: Element,
-    sceneValues: Element.TargetValues,
+    private var layoutImpl: SceneTransitionLayoutImpl,
+    private var scene: Scene,
+    private var element: Element,
+    private var sceneValues: Element.TargetValues,
 ) : Modifier.Node(), DrawModifierNode {
-    private var layoutImpl: SceneTransitionLayoutImpl = layoutImpl
-    private var scene: Scene = scene
-    private var element: Element = element
-    private var sceneValues: Element.TargetValues = sceneValues
 
     override fun onAttach() {
         super.onAttach()
@@ -283,26 +279,27 @@
     scene: Scene,
     element: Element,
 ): Boolean {
-    val state = layoutImpl.state.transitionState
+    val transition = layoutImpl.state.currentTransition
 
     // Always draw the element if there is no ongoing transition or if the element is not shared.
     if (
-        state !is TransitionState.Transition ||
-            !layoutImpl.isTransitionReady(state) ||
-            state.fromScene !in element.sceneValues ||
-            state.toScene !in element.sceneValues
+        transition == null ||
+            !layoutImpl.isTransitionReady(transition) ||
+            transition.fromScene !in element.sceneValues ||
+            transition.toScene !in element.sceneValues
     ) {
         return true
     }
 
-    val sharedTransformation = sharedElementTransformation(layoutImpl, state, element.key)
+    val sharedTransformation =
+        sharedElementTransformation(layoutImpl.state, transition, element.key)
     if (sharedTransformation?.enabled == false) {
         return true
     }
 
     return shouldDrawOrComposeSharedElement(
         layoutImpl,
-        state,
+        transition,
         scene.key,
         element.key,
         sharedTransformation,
@@ -331,21 +328,21 @@
 }
 
 private fun isSharedElementEnabled(
-    layoutImpl: SceneTransitionLayoutImpl,
+    layoutState: SceneTransitionLayoutStateImpl,
     transition: TransitionState.Transition,
     element: ElementKey,
 ): Boolean {
-    return sharedElementTransformation(layoutImpl, transition, element)?.enabled ?: true
+    return sharedElementTransformation(layoutState, transition, element)?.enabled ?: true
 }
 
 internal fun sharedElementTransformation(
-    layoutImpl: SceneTransitionLayoutImpl,
+    layoutState: SceneTransitionLayoutStateImpl,
     transition: TransitionState.Transition,
     element: ElementKey,
 ): SharedElementTransformation? {
-    val spec = layoutImpl.transitions.transitionSpec(transition.fromScene, transition.toScene)
-    val sharedInFromScene = spec.transformations(element, transition.fromScene).shared
-    val sharedInToScene = spec.transformations(element, transition.toScene).shared
+    val transformationSpec = layoutState.transformationSpec
+    val sharedInFromScene = transformationSpec.transformations(element, transition.fromScene).shared
+    val sharedInToScene = transformationSpec.transformations(element, transition.toScene).shared
 
     // The sharedElement() transformation must either be null or be the same in both scenes.
     if (sharedInFromScene != sharedInToScene) {
@@ -371,13 +368,9 @@
     scene: Scene,
     sceneValues: Element.TargetValues,
 ): Boolean {
-    val state = layoutImpl.state.transitionState
+    val transition = layoutImpl.state.currentTransition ?: return true
 
-    if (state !is TransitionState.Transition) {
-        return true
-    }
-
-    if (!layoutImpl.isTransitionReady(state)) {
+    if (!layoutImpl.isTransitionReady(transition)) {
         val lastValue =
             sceneValues.lastValues.alpha.takeIf { it != Element.AlphaUnspecified }
                 ?: element.lastSharedValues.alpha.takeIf { it != Element.AlphaUnspecified } ?: 1f
@@ -385,8 +378,8 @@
         return lastValue == 1f
     }
 
-    val fromScene = state.fromScene
-    val toScene = state.toScene
+    val fromScene = transition.fromScene
+    val toScene = transition.toScene
     val fromValues = element.sceneValues[fromScene]
     val toValues = element.sceneValues[toScene]
 
@@ -395,14 +388,11 @@
     }
 
     val isSharedElement = fromValues != null && toValues != null
-    if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) {
+    if (isSharedElement && isSharedElementEnabled(layoutImpl.state, transition, element.key)) {
         return true
     }
 
-    return layoutImpl.transitions
-        .transitionSpec(fromScene, toScene)
-        .transformations(element.key, scene.key)
-        .alpha == null
+    return layoutImpl.state.transformationSpec.transformations(element.key, scene.key).alpha == null
 }
 
 /**
@@ -607,24 +597,22 @@
     lastValue: () -> T,
     lerp: (T, T, Float) -> T,
 ): T {
-    val state = layoutImpl.state.transitionState
-
-    // There is no ongoing transition.
-    if (state !is TransitionState.Transition) {
-        // Even if this element SceneTransitionLayout is not animated, the layout itself might be
-        // animated (e.g. by another parent SceneTransitionLayout), in which case this element still
-        // need to participate in the layout phase.
-        return currentValue()
-    }
+    val transition =
+        layoutImpl.state.currentTransition
+        // There is no ongoing transition. Even if this element SceneTransitionLayout is not
+        // animated, the layout itself might be animated (e.g. by another parent
+        // SceneTransitionLayout), in which case this element still need to participate in the
+        // layout phase.
+        ?: return currentValue()
 
     // A transition was started but it's not ready yet (not all elements have been composed/laid
     // out yet). Use the last value that was set, to make sure elements don't unexpectedly jump.
-    if (!layoutImpl.isTransitionReady(state)) {
+    if (!layoutImpl.isTransitionReady(transition)) {
         return lastValue()
     }
 
-    val fromScene = state.fromScene
-    val toScene = state.toScene
+    val fromScene = transition.fromScene
+    val toScene = transition.toScene
     val fromValues = element.sceneValues[fromScene]
     val toValues = element.sceneValues[toScene]
 
@@ -638,21 +626,17 @@
     // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared
     // elements follow the finger direction.
     val isSharedElement = fromValues != null && toValues != null
-    if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) {
+    if (isSharedElement && isSharedElementEnabled(layoutImpl.state, transition, element.key)) {
         val start = sceneValue(fromValues!!)
         val end = sceneValue(toValues!!)
 
         // Make sure we don't read progress if values are the same and we don't need to interpolate,
         // so we don't invalidate the phase where this is read.
-        return if (start == end) start else lerp(start, end, state.progress)
+        return if (start == end) start else lerp(start, end, transition.progress)
     }
 
     val transformation =
-        transformation(
-            layoutImpl.transitions
-                .transitionSpec(fromScene, toScene)
-                .transformations(element.key, scene.key)
-        )
+        transformation(layoutImpl.state.transformationSpec.transformations(element.key, scene.key))
         // If there is no transformation explicitly associated to this element value, let's use
         // the value given by the system (like the current position and size given by the layout
         // pass).
@@ -675,7 +659,7 @@
             scene,
             element,
             sceneValues,
-            state,
+            transition,
             idleValue,
         )
 
@@ -685,7 +669,7 @@
         return targetValue
     }
 
-    val progress = state.progress
+    val progress = transition.progress
     // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range.
     val rangeProgress = transformation.range?.progress(progress) ?: progress
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index 7029da2..306f276 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -120,17 +120,13 @@
     scene: SceneKey,
     element: Element,
 ): Boolean {
-    val transitionState = layoutImpl.state.transitionState
-
-    // If we are idle, there is only one [scene] that is composed so we can compose our movable
-    // content here.
-    if (transitionState is TransitionState.Idle) {
-        check(transitionState.currentScene == scene)
-        return true
-    }
-
-    val fromScene = (transitionState as TransitionState.Transition).fromScene
-    val toScene = transitionState.toScene
+    val transition =
+        layoutImpl.state.currentTransition
+        // If we are idle, there is only one [scene] that is composed so we can compose our
+        // movable content here.
+        ?: return true
+    val fromScene = transition.fromScene
+    val toScene = transition.toScene
 
     val fromReady = layoutImpl.isSceneReady(fromScene)
     val toReady = layoutImpl.isSceneReady(toScene)
@@ -181,10 +177,10 @@
 
     return shouldDrawOrComposeSharedElement(
         layoutImpl,
-        transitionState,
+        transition,
         scene,
         element.key,
-        sharedElementTransformation(layoutImpl, transitionState, element.key),
+        sharedElementTransformation(layoutImpl.state, transition, element.key),
     )
 }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index 32025b4..e78f326 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -179,7 +179,8 @@
     bottomOrRightBehavior: NestedScrollBehavior,
 ) =
     SceneNestedScrollHandler(
-            gestureHandler = layoutImpl.gestureHandler(orientation = orientation),
+            layoutImpl = layoutImpl,
+            orientation = orientation,
             topOrLeftBehavior = topOrLeftBehavior,
             bottomOrRightBehavior = bottomOrRightBehavior,
         )
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 91decf4..338557d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -41,14 +41,9 @@
     internal val orientation: Orientation,
     private val coroutineScope: CoroutineScope,
 ) {
+    private val layoutState = layoutImpl.state
     val draggable: DraggableHandler = SceneDraggableHandler(this)
 
-    internal var transitionState
-        get() = layoutImpl.state.transitionState
-        set(value) {
-            layoutImpl.state.transitionState = value
-        }
-
     private var _swipeTransition: SwipeTransition? = null
     internal var swipeTransition: SwipeTransition
         get() = _swipeTransition ?: error("SwipeTransition needs to be initialized")
@@ -57,27 +52,26 @@
         }
 
     private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
-        if (isDrivingTransition || force) transitionState = newTransition
+        if (isDrivingTransition || force) layoutState.startTransition(newTransition)
         swipeTransition = newTransition
     }
 
-    internal val currentScene: Scene
-        get() = layoutImpl.scene(transitionState.currentScene)
-
     internal val isDrivingTransition
-        get() = transitionState == _swipeTransition
+        get() = layoutState.transitionState == _swipeTransition
 
     /**
      * The velocity threshold at which the intent of the user is to swipe up or down. It is the same
      * as SwipeableV2Defaults.VelocityThreshold.
      */
-    internal val velocityThreshold = with(layoutImpl.density) { 125.dp.toPx() }
+    internal val velocityThreshold: Float
+        get() = with(layoutImpl.density) { 125.dp.toPx() }
 
     /**
      * The positional threshold at which the intent of the user is to swipe to the next scene. It is
      * the same as SwipeableV2Defaults.PositionalThreshold.
      */
-    private val positionalThreshold = with(layoutImpl.density) { 56.dp.toPx() }
+    private val positionalThreshold
+        get() = with(layoutImpl.density) { 56.dp.toPx() }
 
     internal var gestureWithPriority: Any? = null
 
@@ -98,18 +92,18 @@
             return
         }
 
-        val transition = transitionState
-        if (transition is TransitionState.Transition) {
+        val transitionState = layoutState.transitionState
+        if (transitionState is TransitionState.Transition) {
             // TODO(b/290184746): Better handle interruptions here if state != idle.
             Log.w(
                 TAG,
                 "start from TransitionState.Transition is not fully supported: from" +
-                    " ${transition.fromScene} to ${transition.toScene} " +
-                    "(progress ${transition.progress})"
+                    " ${transitionState.fromScene} to ${transitionState.toScene} " +
+                    "(progress ${transitionState.progress})"
             )
         }
 
-        val fromScene = currentScene
+        val fromScene = layoutImpl.scene(transitionState.currentScene)
         setCurrentActions(fromScene, startedPosition, pointersDown)
 
         val (targetScene, distance) =
@@ -364,7 +358,7 @@
                     findTargetSceneAndDistanceStrict(fromScene, velocity)
                         ?: run {
                             // We will not animate
-                            transitionState = TransitionState.Idle(fromScene.key)
+                            layoutState.finishTransition(swipeTransition, idleScene = fromScene.key)
                             return
                         }
 
@@ -439,14 +433,7 @@
                 )
 
                 swipeTransition.finishOffsetAnimation()
-
-                // Now that the animation is done, the state should be idle. Note that if the state
-                // was changed since this animation started, some external code changed it and we
-                // shouldn't do anything here. Note also that this job will be cancelled in the case
-                // where the user intercepts this swipe.
-                if (isDrivingTransition) {
-                    transitionState = TransitionState.Idle(targetScene)
-                }
+                layoutState.finishTransition(swipeTransition, targetScene)
             }
         }
     }
@@ -539,10 +526,14 @@
 }
 
 internal class SceneNestedScrollHandler(
-    private val gestureHandler: SceneGestureHandler,
+    private val layoutImpl: SceneTransitionLayoutImpl,
+    private val orientation: Orientation,
     private val topOrLeftBehavior: NestedScrollBehavior,
     private val bottomOrRightBehavior: NestedScrollBehavior,
 ) : NestedScrollHandler {
+    private val layoutState = layoutImpl.state
+    private val gestureHandler = layoutImpl.gestureHandler(orientation)
+
     override val connection: PriorityNestedScrollConnection = nestedScrollConnection()
 
     private fun nestedScrollConnection(): PriorityNestedScrollConnection {
@@ -553,7 +544,7 @@
         val actionUpOrLeft =
             Swipe(
                 direction =
-                    when (gestureHandler.orientation) {
+                    when (orientation) {
                         Orientation.Horizontal -> SwipeDirection.Left
                         Orientation.Vertical -> SwipeDirection.Up
                     },
@@ -563,7 +554,7 @@
         val actionDownOrRight =
             Swipe(
                 direction =
-                    when (gestureHandler.orientation) {
+                    when (orientation) {
                         Orientation.Horizontal -> SwipeDirection.Right
                         Orientation.Vertical -> SwipeDirection.Down
                     },
@@ -571,7 +562,7 @@
             )
 
         fun hasNextScene(amount: Float): Boolean {
-            val fromScene = gestureHandler.currentScene
+            val fromScene = layoutImpl.scene(layoutState.transitionState.currentScene)
             val nextScene =
                 when {
                     amount < 0f -> fromScene.userActions[actionUpOrLeft]
@@ -582,7 +573,7 @@
         }
 
         return PriorityNestedScrollConnection(
-            orientation = gestureHandler.orientation,
+            orientation = orientation,
             canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
                 canChangeScene = offsetBeforeStart == 0f
 
@@ -590,8 +581,9 @@
                     canChangeScene && gestureHandler.isDrivingTransition && offsetAvailable != 0f
                 if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
 
-                val progress = gestureHandler.swipeTransition.progress
-                val threshold = gestureHandler.layoutImpl.transitionInterceptionThreshold
+                val swipeTransition = gestureHandler.swipeTransition
+                val progress = swipeTransition.progress
+                val threshold = layoutImpl.transitionInterceptionThreshold
                 fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold
 
                 // The transition is always between 0 and 1. If it is close to either of these
@@ -599,9 +591,8 @@
                 // The progress value can go beyond this range in the case of overscroll.
                 val shouldSnapToIdle = isProgressCloseTo(0f) || isProgressCloseTo(1f)
                 if (shouldSnapToIdle) {
-                    gestureHandler.swipeTransition.cancelOffsetAnimation()
-                    gestureHandler.transitionState =
-                        TransitionState.Idle(gestureHandler.swipeTransition.currentScene)
+                    swipeTransition.cancelOffsetAnimation()
+                    layoutState.finishTransition(swipeTransition, swipeTransition.currentScene)
                 }
 
                 // Start only if we cannot consume this event
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 239971f..3608e37 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -19,6 +19,8 @@
 import androidx.annotation.FloatRange
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.remember
@@ -27,6 +29,7 @@
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.platform.LocalDensity
+import kotlinx.coroutines.channels.Channel
 
 /**
  * [SceneTransitionLayout] is a container that automatically animates its content whenever
@@ -266,24 +269,45 @@
     val coroutineScope = rememberCoroutineScope()
     val layoutImpl = remember {
         SceneTransitionLayoutImpl(
+                state = state as SceneTransitionLayoutStateImpl,
                 onChangeScene = onChangeScene,
-                builder = scenes,
-                transitions = transitions,
-                state = state,
                 density = density,
                 edgeDetector = edgeDetector,
                 transitionInterceptionThreshold = transitionInterceptionThreshold,
+                builder = scenes,
                 coroutineScope = coroutineScope,
             )
             .also { onLayoutImpl?.invoke(it) }
     }
 
-    layoutImpl.onChangeScene = onChangeScene
-    layoutImpl.transitions = transitions
-    layoutImpl.density = density
-    layoutImpl.edgeDetector = edgeDetector
+    val targetSceneChannel = remember { Channel<SceneKey>(Channel.CONFLATED) }
+    SideEffect {
+        if (state != layoutImpl.state) {
+            error(
+                "This SceneTransitionLayout was bound to a different SceneTransitionLayoutState" +
+                    " that was used when creating it, which is not supported"
+            )
+        }
 
-    layoutImpl.setScenes(scenes)
-    layoutImpl.setCurrentScene(currentScene)
+        layoutImpl.onChangeScene = onChangeScene
+        (state as SceneTransitionLayoutStateImpl).transitions = transitions
+        layoutImpl.density = density
+        layoutImpl.edgeDetector = edgeDetector
+        layoutImpl.updateScenes(scenes)
+
+        state.transitions = transitions
+
+        targetSceneChannel.trySend(currentScene)
+    }
+
+    LaunchedEffect(targetSceneChannel) {
+        for (newKey in targetSceneChannel) {
+            // Inspired by AnimateAsState.kt: let's poll the last value to avoid being one frame
+            // late.
+            val newKey = targetSceneChannel.tryReceive().getOrNull() ?: newKey
+            animateToScene(layoutImpl.state, newKey)
+        }
+    }
+
     layoutImpl.Content(modifier)
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 00e33e2..c99c325 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -22,13 +22,8 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.key
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshots.SnapshotStateMap
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
@@ -40,36 +35,40 @@
 import androidx.compose.ui.util.fastForEach
 import com.android.compose.ui.util.lerp
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.Channel
 
 @Stable
 internal class SceneTransitionLayoutImpl(
-    onChangeScene: (SceneKey) -> Unit,
+    internal val state: SceneTransitionLayoutStateImpl,
+    internal var onChangeScene: (SceneKey) -> Unit,
+    internal var density: Density,
+    internal var edgeDetector: EdgeDetector,
+    internal var transitionInterceptionThreshold: Float,
     builder: SceneTransitionLayoutScope.() -> Unit,
-    transitions: SceneTransitions,
-    internal val state: SceneTransitionLayoutState,
-    density: Density,
-    edgeDetector: EdgeDetector,
-    transitionInterceptionThreshold: Float,
     coroutineScope: CoroutineScope,
 ) {
-    internal val scenes = SnapshotStateMap<SceneKey, Scene>()
+    internal val scenes = mutableMapOf<SceneKey, Scene>()
+
+    /**
+     * The map of [Element]s.
+     *
+     * Note that this map is *mutated* directly during composition, so it is a [SnapshotStateMap] to
+     * make sure that mutations are reverted if composition is cancelled.
+     */
     internal val elements = SnapshotStateMap<ElementKey, Element>()
 
-    /** The scenes that are "ready", i.e. they were composed and fully laid-out at least once. */
+    /**
+     * The scenes that are "ready", i.e. they were composed and fully laid-out at least once.
+     *
+     * Note that this map is *read* during composition, so it is a [SnapshotStateMap] to make sure
+     * that we recompose when modifications are made to this map.
+     */
     private val readyScenes = SnapshotStateMap<SceneKey, Boolean>()
 
-    internal var onChangeScene by mutableStateOf(onChangeScene)
-    internal var transitions by mutableStateOf(transitions)
-    internal var density: Density by mutableStateOf(density)
-    internal var edgeDetector by mutableStateOf(edgeDetector)
-    internal var transitionInterceptionThreshold by mutableStateOf(transitionInterceptionThreshold)
-
     private val horizontalGestureHandler: SceneGestureHandler
     private val verticalGestureHandler: SceneGestureHandler
 
     init {
-        setScenes(builder)
+        updateScenes(builder)
 
         // SceneGestureHandler must wait for the scenes to be initialized, in order to access the
         // current scene (required for SwipeTransition).
@@ -98,7 +97,7 @@
         return scenes[key] ?: error("Scene $key is not configured")
     }
 
-    internal fun setScenes(builder: SceneTransitionLayoutScope.() -> Unit) {
+    internal fun updateScenes(builder: SceneTransitionLayoutScope.() -> Unit) {
         // Keep a reference of the current scenes. After processing [builder], the scenes that were
         // not configured will be removed.
         val scenesToRemove = scenes.keys.toMutableSet()
@@ -141,20 +140,6 @@
     }
 
     @Composable
-    internal fun setCurrentScene(key: SceneKey) {
-        val channel = remember { Channel<SceneKey>(Channel.CONFLATED) }
-        SideEffect { channel.trySend(key) }
-        LaunchedEffect(channel) {
-            for (newKey in channel) {
-                // Inspired by AnimateAsState.kt: let's poll the last value to avoid being one frame
-                // late.
-                val newKey = channel.tryReceive().getOrNull() ?: newKey
-                animateToScene(this@SceneTransitionLayoutImpl, newKey)
-            }
-        }
-    }
-
-    @Composable
     @OptIn(ExperimentalComposeUiApi::class)
     internal fun Content(modifier: Modifier) {
         Box(
@@ -171,14 +156,14 @@
 
                     val width: Int
                     val height: Int
-                    val state = state.transitionState
-                    if (state !is TransitionState.Transition) {
+                    val transition = state.currentTransition
+                    if (transition == null) {
                         width = placeable.width
                         height = placeable.height
                     } else {
                         // Interpolate the size.
-                        val fromSize = scene(state.fromScene).targetSize
-                        val toSize = scene(state.toScene).targetSize
+                        val fromSize = scene(transition.fromScene).targetSize
+                        val toSize = scene(transition.toScene).targetSize
 
                         // Optimization: make sure we don't read state.progress if fromSize ==
                         // toSize to avoid running this code every frame when the layout size does
@@ -187,7 +172,7 @@
                             width = fromSize.width
                             height = fromSize.height
                         } else {
-                            val size = lerp(fromSize, toSize, state.progress)
+                            val size = lerp(fromSize, toSize, transition.progress)
                             width = size.width.coerceAtLeast(0)
                             height = size.height.coerceAtLeast(0)
                         }
@@ -228,13 +213,12 @@
 
                             scene.Content(
                                 Modifier.drawWithContent {
-                                    when (val state = state.transitionState) {
-                                        is TransitionState.Idle -> drawContent()
-                                        is TransitionState.Transition -> {
-                                            // Don't draw scenes that are not ready yet.
-                                            if (readyScenes.containsKey(key)) {
-                                                drawContent()
-                                            }
+                                    if (state.currentTransition == null) {
+                                        drawContent()
+                                    } else {
+                                        // Don't draw scenes that are not ready yet.
+                                        if (readyScenes.containsKey(key)) {
+                                            drawContent()
                                         }
                                     }
                                 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index 62372558..d1ba582 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -23,31 +23,32 @@
 
 /** The state of a [SceneTransitionLayout]. */
 @Stable
-class SceneTransitionLayoutState(initialScene: SceneKey) {
+sealed interface SceneTransitionLayoutState {
     /**
      * The current [TransitionState]. All values read here are backed by the Snapshot system.
      *
      * To observe those values outside of Compose/the Snapshot system, use
      * [SceneTransitionLayoutState.observableTransitionState] instead.
      */
-    var transitionState: TransitionState by mutableStateOf(TransitionState.Idle(initialScene))
+    val transitionState: TransitionState
+
+    /** The current transition, or `null` if we are idle. */
+    val currentTransition: TransitionState.Transition?
+        get() = transitionState as? TransitionState.Transition
 
     /**
-     * Whether we are transitioning, optionally restricting the check to the transition between
-     * [from] and [to].
+     * Whether we are transitioning. If [from] or [to] is empty, we will also check that they match
+     * the scenes we are animating from and/or to.
      */
-    fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean {
-        val transition = transitionState as? TransitionState.Transition ?: return false
-
-        return (from == null || transition.fromScene == from) &&
-            (to == null || transition.toScene == to)
-    }
+    fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean
 
     /** Whether we are transitioning from [scene] to [other], or from [other] to [scene]. */
-    fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean {
-        return isTransitioning(from = scene, to = other) ||
-            isTransitioning(from = other, to = scene)
-    }
+    fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean
+}
+
+/** Create a new [SceneTransitionLayoutState] that is currently idle at scene [currentScene]. */
+fun SceneTransitionLayoutState(currentScene: SceneKey): SceneTransitionLayoutState {
+    return SceneTransitionLayoutStateImpl(currentScene, SceneTransitions.Empty)
 }
 
 @Stable
@@ -93,3 +94,50 @@
         abstract val isUserInputOngoing: Boolean
     }
 }
+
+internal class SceneTransitionLayoutStateImpl(
+    initialScene: SceneKey,
+    internal var transitions: SceneTransitions,
+) : SceneTransitionLayoutState {
+    override var transitionState: TransitionState by
+        mutableStateOf(TransitionState.Idle(initialScene))
+        private set
+
+    /**
+     * The current [transformationSpec] associated to [transitionState]. Accessing this value makes
+     * sense only if [transitionState] is a [TransitionState.Transition].
+     */
+    internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
+
+    override fun isTransitioning(from: SceneKey?, to: SceneKey?): Boolean {
+        val transition = currentTransition ?: return false
+        return (from == null || transition.fromScene == from) &&
+            (to == null || transition.toScene == to)
+    }
+
+    override fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean {
+        return isTransitioning(from = scene, to = other) ||
+            isTransitioning(from = other, to = scene)
+    }
+
+    /** Start a new [transition], instantly interrupting any ongoing transition if there was one. */
+    internal fun startTransition(transition: TransitionState.Transition) {
+        // Compute the [TransformationSpec] when the transition starts.
+        transformationSpec =
+            transitions
+                .transitionSpec(transition.fromScene, transition.toScene)
+                .transformationSpec()
+
+        transitionState = transition
+    }
+
+    /**
+     * Notify that [transition] was finished and that we should settle to [idleScene]. This will do
+     * nothing if [transition] was interrupted since it was started.
+     */
+    internal fun finishTransition(transition: TransitionState.Transition, idleScene: SceneKey) {
+        if (transitionState == transition) {
+            transitionState = TransitionState.Idle(idleScene)
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index f91895b..3a55567 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -18,11 +18,9 @@
 
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.snap
-import androidx.compose.runtime.Stable
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastMap
 import com.android.compose.animation.scene.transformation.AnchoredSize
 import com.android.compose.animation.scene.transformation.AnchoredTranslate
 import com.android.compose.animation.scene.transformation.DrawScale
@@ -36,16 +34,17 @@
 import com.android.compose.animation.scene.transformation.Translate
 
 /** The transitions configuration of a [SceneTransitionLayout]. */
-class SceneTransitions(
-    internal val transitionSpecs: List<TransitionSpec>,
+class SceneTransitions
+internal constructor(
+    internal val transitionSpecs: List<TransitionSpecImpl>,
 ) {
-    private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpec>>()
+    private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpecImpl>>()
 
-    internal fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpec {
+    internal fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpecImpl {
         return cache.getOrPut(from) { mutableMapOf() }.getOrPut(to) { findSpec(from, to) }
     }
 
-    private fun findSpec(from: SceneKey, to: SceneKey): TransitionSpec {
+    private fun findSpec(from: SceneKey, to: SceneKey): TransitionSpecImpl {
         val spec = transition(from, to) { it.from == from && it.to == to }
         if (spec != null) {
             return spec
@@ -53,7 +52,7 @@
 
         val reversed = transition(from, to) { it.from == to && it.to == from }
         if (reversed != null) {
-            return reversed.reverse()
+            return reversed.reversed()
         }
 
         val relaxedSpec =
@@ -67,16 +66,16 @@
         return transition(from, to) {
                 (it.from == to && it.to == null) || (it.to == from && it.from == null)
             }
-            ?.reverse()
+            ?.reversed()
             ?: defaultTransition(from, to)
     }
 
     private fun transition(
         from: SceneKey,
         to: SceneKey,
-        filter: (TransitionSpec) -> Boolean,
-    ): TransitionSpec? {
-        var match: TransitionSpec? = null
+        filter: (TransitionSpecImpl) -> Boolean,
+    ): TransitionSpecImpl? {
+        var match: TransitionSpecImpl? = null
         transitionSpecs.fastForEach { spec ->
             if (filter(spec)) {
                 if (match != null) {
@@ -89,28 +88,88 @@
     }
 
     private fun defaultTransition(from: SceneKey, to: SceneKey) =
-        TransitionSpec(from, to, emptyList(), snap())
+        TransitionSpecImpl(from, to, TransformationSpec.EmptyProvider)
+
+    companion object {
+        val Empty = SceneTransitions(transitionSpecs = emptyList())
+    }
 }
 
 /** The definition of a transition between [from] and [to]. */
-@Stable
-data class TransitionSpec(
-    val from: SceneKey?,
-    val to: SceneKey?,
-    val transformations: List<Transformation>,
-    val spec: AnimationSpec<Float>,
-) {
-    // TODO(b/302300957): Make sure this cache does not infinitely grow.
-    private val cache = mutableMapOf<ElementKey, MutableMap<SceneKey, ElementTransformations>>()
+interface TransitionSpec {
+    /**
+     * The scene we are transitioning from. If `null`, this spec can be used to animate from any
+     * scene.
+     */
+    val from: SceneKey?
 
-    internal fun reverse(): TransitionSpec {
-        return copy(
+    /**
+     * The scene we are transitioning to. If `null`, this spec can be used to animate from any
+     * scene.
+     */
+    val to: SceneKey?
+
+    /**
+     * Return a reversed version of this [TransitionSpec] for a transition going from [to] to
+     * [from].
+     */
+    fun reversed(): TransitionSpec
+
+    /*
+     * The [TransformationSpec] associated to this [TransitionSpec].
+     *
+     * Note that this is called once every a transition associated to this [TransitionSpec] is
+     * started.
+     */
+    fun transformationSpec(): TransformationSpec
+}
+
+interface TransformationSpec {
+    /** The [AnimationSpec] used to animate the associated transition progress. */
+    val progressSpec: AnimationSpec<Float>
+
+    /** The list of [Transformation] applied to elements during this transition. */
+    val transformations: List<Transformation>
+
+    companion object {
+        internal val Empty =
+            TransformationSpecImpl(progressSpec = snap(), transformations = emptyList())
+        internal val EmptyProvider = { Empty }
+    }
+}
+
+internal class TransitionSpecImpl(
+    override val from: SceneKey?,
+    override val to: SceneKey?,
+    private val transformationSpec: () -> TransformationSpecImpl,
+) : TransitionSpec {
+    override fun reversed(): TransitionSpecImpl {
+        return TransitionSpecImpl(
             from = to,
             to = from,
-            transformations = transformations.fastMap { it.reverse() },
+            transformationSpec = {
+                val reverse = transformationSpec.invoke()
+                TransformationSpecImpl(
+                    progressSpec = reverse.progressSpec,
+                    transformations = reverse.transformations.map { it.reversed() }
+                )
+            }
         )
     }
 
+    override fun transformationSpec(): TransformationSpecImpl = this.transformationSpec.invoke()
+}
+
+/**
+ * An implementation of [TransformationSpec] that allows the quick retrieval of an element
+ * [ElementTransformations].
+ */
+internal class TransformationSpecImpl(
+    override val progressSpec: AnimationSpec<Float>,
+    override val transformations: List<Transformation>,
+) : TransformationSpec {
+    private val cache = mutableMapOf<ElementKey, MutableMap<SceneKey, ElementTransformations>>()
+
     internal fun transformations(element: ElementKey, scene: SceneKey): ElementTransformations {
         return cache
             .getOrPut(element) { mutableMapOf() }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 116a666..0d3bc7d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -27,7 +27,8 @@
     fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean =
         userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
 
-    val currentScene = gestureHandler.currentScene
+    val layoutImpl = gestureHandler.layoutImpl
+    val currentScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
     val orientation = gestureHandler.orientation
     val canSwipe = currentScene.shouldEnableSwipes(orientation)
     val canOppositeSwipe =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 8f4a36e..7046866 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -44,7 +44,7 @@
 }
 
 private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
-    val transitionSpecs = mutableListOf<TransitionSpec>()
+    val transitionSpecs = mutableListOf<TransitionSpecImpl>()
 
     override fun to(to: SceneKey, builder: TransitionBuilder.() -> Unit): TransitionSpec {
         return transition(from = null, to = to, builder)
@@ -63,14 +63,15 @@
         to: SceneKey?,
         builder: TransitionBuilder.() -> Unit,
     ): TransitionSpec {
-        val impl = TransitionBuilderImpl().apply(builder)
-        val spec =
-            TransitionSpec(
-                from,
-                to,
-                impl.transformations,
-                impl.spec,
+        fun transformationSpec(): TransformationSpecImpl {
+            val impl = TransitionBuilderImpl().apply(builder)
+            return TransformationSpecImpl(
+                progressSpec = impl.spec,
+                transformations = impl.transformations,
             )
+        }
+
+        val spec = TransitionSpecImpl(from, to, ::transformationSpec)
         transitionSpecs.add(spec)
         return spec
     }
@@ -143,7 +144,7 @@
 
         transformations.add(
             if (reversed) {
-                transformation.reverse()
+                transformation.reversed()
             } else {
                 transformation
             }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index 2069355..0cd11b9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -42,7 +42,7 @@
      * Reverse this transformation. This is called when we use Transition(from = A, to = B) when
      * animating from B to A and there is no Transition(from = B, to = A) defined.
      */
-    fun reverse(): Transformation = this
+    fun reversed(): Transformation = this
 }
 
 internal class SharedElementTransformation(
@@ -77,10 +77,10 @@
     val delegate: PropertyTransformation<T>,
     override val range: TransformationRange,
 ) : PropertyTransformation<T> by delegate {
-    override fun reverse(): Transformation {
+    override fun reversed(): Transformation {
         return RangedPropertyTransformation(
-            delegate.reverse() as PropertyTransformation<T>,
-            range.reverse()
+            delegate.reversed() as PropertyTransformation<T>,
+            range.reversed()
         )
     }
 }
@@ -102,7 +102,7 @@
     }
 
     /** Reverse this range. */
-    fun reverse() = TransformationRange(start = reverseBound(end), end = reverseBound(start))
+    fun reversed() = TransformationRange(start = reverseBound(end), end = reverseBound(start))
 
     /** Get the progress of this range given the global [transitionProgress]. */
     fun progress(transitionProgress: Float): Float {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index e6224df..d9ce519 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -55,8 +55,8 @@
     ) {
         private var internalCurrentScene: SceneKey by mutableStateOf(SceneA)
 
-        private val layoutState: SceneTransitionLayoutState =
-            SceneTransitionLayoutState(internalCurrentScene)
+        private val layoutState =
+            SceneTransitionLayoutStateImpl(internalCurrentScene, EmptyTestTransitions)
 
         val mutableUserActionsA: MutableMap<UserAction, SceneKey> =
             mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
@@ -93,36 +93,24 @@
 
         private val layoutImpl =
             SceneTransitionLayoutImpl(
-                    onChangeScene = { internalCurrentScene = it },
-                    builder = scenesBuilder,
-                    transitions = EmptyTestTransitions,
                     state = layoutState,
+                    onChangeScene = { internalCurrentScene = it },
                     density = Density(1f),
                     edgeDetector = DefaultEdgeDetector,
                     transitionInterceptionThreshold = transitionInterceptionThreshold,
+                    builder = scenesBuilder,
                     coroutineScope = coroutineScope,
                 )
                 .apply { setScenesTargetSizeForTest(LAYOUT_SIZE) }
 
-        val sceneGestureHandler =
-            SceneGestureHandler(
-                layoutImpl = layoutImpl,
-                orientation = Orientation.Vertical,
-                coroutineScope = coroutineScope,
-            )
-
-        val horizontalSceneGestureHandler =
-            SceneGestureHandler(
-                layoutImpl = layoutImpl,
-                orientation = Orientation.Horizontal,
-                coroutineScope = coroutineScope,
-            )
-
+        val sceneGestureHandler = layoutImpl.gestureHandler(Orientation.Vertical)
+        val horizontalSceneGestureHandler = layoutImpl.gestureHandler(Orientation.Horizontal)
         val draggable = sceneGestureHandler.draggable
 
         fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) =
             SceneNestedScrollHandler(
-                    gestureHandler = sceneGestureHandler,
+                    layoutImpl,
+                    orientation = sceneGestureHandler.orientation,
                     topOrLeftBehavior = nestedScrollBehavior,
                     bottomOrRightBehavior = nestedScrollBehavior,
                 )
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index eeda8d46..c5b8d9a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -40,8 +40,8 @@
 
     @Test
     fun isTransitioningTo_transition() {
-        val state = SceneTransitionLayoutState(TestScenes.SceneA)
-        state.transitionState = transition(from = TestScenes.SceneA, to = TestScenes.SceneB)
+        val state = SceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+        state.startTransition(transition(from = TestScenes.SceneA, to = TestScenes.SceneB))
 
         assertThat(state.isTransitioning()).isTrue()
         assertThat(state.isTransitioning(from = TestScenes.SceneA)).isTrue()
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index fa94b250..ef72992 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -55,7 +55,7 @@
 
         assertThat(transitions.transitionSpecs)
             .comparingElementsUsing(
-                Correspondence.transforming<TransitionSpec, Pair<SceneKey?, SceneKey?>>(
+                Correspondence.transforming<TransitionSpecImpl, Pair<SceneKey?, SceneKey?>>(
                     { it?.from to it?.to },
                     "has (from, to) equal to"
                 )
@@ -70,8 +70,8 @@
     @Test
     fun defaultTransitionSpec() {
         val transitions = transitions { from(TestScenes.SceneA, to = TestScenes.SceneB) }
-        val transition = transitions.transitionSpecs.single()
-        assertThat(transition.spec).isInstanceOf(SpringSpec::class.java)
+        val transformationSpec = transitions.transitionSpecs.single().transformationSpec()
+        assertThat(transformationSpec.progressSpec).isInstanceOf(SpringSpec::class.java)
     }
 
     @Test
@@ -79,9 +79,9 @@
         val transitions = transitions {
             from(TestScenes.SceneA, to = TestScenes.SceneB) { spec = tween(durationMillis = 42) }
         }
-        val transition = transitions.transitionSpecs.single()
-        assertThat(transition.spec).isInstanceOf(TweenSpec::class.java)
-        assertThat((transition.spec as TweenSpec).durationMillis).isEqualTo(42)
+        val transformationSpec = transitions.transitionSpecs.single().transformationSpec()
+        assertThat(transformationSpec.progressSpec).isInstanceOf(TweenSpec::class.java)
+        assertThat((transformationSpec.progressSpec as TweenSpec).durationMillis).isEqualTo(42)
     }
 
     @Test
@@ -90,9 +90,10 @@
             from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) }
         }
 
-        val transition = transitions.transitionSpecs.single()
-        assertThat(transition.transformations.size).isEqualTo(1)
-        assertThat(transition.transformations.single().range).isEqualTo(null)
+        val transformations =
+            transitions.transitionSpecs.single().transformationSpec().transformations
+        assertThat(transformations.size).isEqualTo(1)
+        assertThat(transformations.single().range).isEqualTo(null)
     }
 
     @Test
@@ -105,8 +106,9 @@
             }
         }
 
-        val transition = transitions.transitionSpecs.single()
-        assertThat(transition.transformations)
+        val transformations =
+            transitions.transitionSpecs.single().transformationSpec().transformations
+        assertThat(transformations)
             .comparingElementsUsing(TRANSFORMATION_RANGE)
             .containsExactly(
                 TransformationRange(start = 0.1f, end = 0.8f),
@@ -127,8 +129,9 @@
             }
         }
 
-        val transition = transitions.transitionSpecs.single()
-        assertThat(transition.transformations)
+        val transformations =
+            transitions.transitionSpecs.single().transformationSpec().transformations
+        assertThat(transformations)
             .comparingElementsUsing(TRANSFORMATION_RANGE)
             .containsExactly(
                 TransformationRange(start = 100 / 500f, end = 300 / 500f),
@@ -149,8 +152,9 @@
             }
         }
 
-        val transition = transitions.transitionSpecs.single()
-        assertThat(transition.transformations)
+        val transformations =
+            transitions.transitionSpecs.single().transformationSpec().transformations
+        assertThat(transformations)
             .comparingElementsUsing(TRANSFORMATION_RANGE)
             .containsExactly(
                 TransformationRange(start = 1f - 0.8f, end = 1f - 0.1f),
@@ -170,9 +174,13 @@
 
         // Fetch the transition from B to A, which will automatically reverse the transition from A
         // to B we defined.
-        val transition =
-            transitions.transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA)
-        assertThat(transition.transformations)
+        val transformations =
+            transitions
+                .transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA)
+                .transformationSpec()
+                .transformations
+
+        assertThat(transformations)
             .comparingElementsUsing(TRANSFORMATION_RANGE)
             .containsExactly(
                 TransformationRange(start = 1f - 0.8f, end = 1f - 0.1f),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 33dd3d9..edf9648 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -35,7 +35,7 @@
 import static android.view.WindowManager.TransitionOldType;
 import static android.view.WindowManager.TransitionType;
 
-import static com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER;
+import static com.android.systemui.Flags.refactorGetCurrentUser;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -609,7 +609,7 @@
         public void setCurrentUser(int userId) {
             trace("Deprecated/NOT USED: setCurrentUser userId=" + userId);
             checkPermission();
-            if (!mFlags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+            if (!refactorGetCurrentUser()) {
                 mKeyguardViewMediator.setCurrentUser(userId);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 57f3b70..d7a1906 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -38,7 +38,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
-import static com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER;
+import static com.android.systemui.Flags.refactorGetCurrentUser;
 import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
@@ -617,7 +617,7 @@
         public void onUserSwitching(int userId) {
             if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId));
             synchronized (KeyguardViewMediator.this) {
-                if (mFeatureFlags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+                if (refactorGetCurrentUser()) {
                     notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId));
                 }
                 resetKeyguardDonePendingLocked();
@@ -1502,7 +1502,7 @@
 
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
 
-        if (!mFeatureFlags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+        if (!refactorGetCurrentUser()) {
             KeyguardUpdateMonitor.setCurrentUser(mUserTracker.getUserId());
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
index 3ed05aa..0fb4b43 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt
@@ -2,9 +2,8 @@
 
 import android.annotation.UserIdInt
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags.refactorGetCurrentUser
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER
 import com.android.systemui.user.data.repository.UserRepository
 import javax.inject.Inject
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -12,12 +11,7 @@
 
 /** Encapsulates business logic to interact the selected user */
 @SysUISingleton
-class SelectedUserInteractor
-@Inject
-constructor(
-    private val repository: UserRepository,
-    private val flags: FeatureFlagsClassic,
-) {
+class SelectedUserInteractor @Inject constructor(private val repository: UserRepository) {
 
     /** Flow providing the ID of the currently selected user. */
     val selectedUser = repository.selectedUserInfo.map { it.id }.distinctUntilChanged()
@@ -34,7 +28,7 @@
     @UserIdInt
     @JvmOverloads
     fun getSelectedUserId(bypassFlag: Boolean = false): Int {
-        return if (bypassFlag || flags.isEnabled(REFACTOR_GETCURRENTUSER)) {
+        return if (bypassFlag || refactorGetCurrentUser()) {
             repository.getSelectedUserInfo().id
         } else {
             KeyguardUpdateMonitor.getCurrentUser()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index 8e54eb7..4c510ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -27,6 +27,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.keyguard.logging.KeyguardLogger
+import com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FeatureFlags
@@ -40,6 +41,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.leak.RotationUtils
 import com.android.systemui.util.mockito.any
+import javax.inject.Provider
 import org.junit.After
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -51,14 +53,14 @@
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 import org.mockito.MockitoSession
 import org.mockito.quality.Strictness
-import javax.inject.Provider
+
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -246,6 +248,7 @@
     @Test
     @RunWithLooper(setAsMainLooper = true)
     fun testAnimatorRunWhenWakeAndUnlock_fingerprint() {
+        mSetFlagsRule.disableFlags(FLAG_LIGHT_REVEAL_MIGRATION)
         val fpsLocation = Point(5, 5)
         `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
         controller.onViewAttached()
@@ -266,6 +269,7 @@
     @Test
     @RunWithLooper(setAsMainLooper = true)
     fun testAnimatorRunWhenWakeAndUnlock_faceUdfpsFingerDown() {
+        mSetFlagsRule.disableFlags(FLAG_LIGHT_REVEAL_MIGRATION)
         val faceLocation = Point(5, 5)
         `when`(authController.faceSensorLocation).thenReturn(faceLocation)
         controller.onViewAttached()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 4ef18cf..b38c9ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -24,6 +24,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import static com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER;
 import static com.android.systemui.keyguard.KeyguardViewMediator.DELAYED_KEYGUARD_ACTION;
 import static com.android.systemui.keyguard.KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT;
 import static com.android.systemui.keyguard.KeyguardViewMediator.REBOOT_MAINLINE_UPDATE;
@@ -266,7 +267,7 @@
                 mSceneContainerFlags);
         mFeatureFlags = new FakeFeatureFlags();
         mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
-        mFeatureFlags.set(Flags.REFACTOR_GETCURRENTUSER, true);
+        mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER);
 
         DejankUtils.setImmediate(true);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
index 8dea57c..8e81185 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -20,8 +20,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -46,11 +44,7 @@
 class FromPrimaryBouncerTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
     private lateinit var underTest: FromPrimaryBouncerTransitionInteractor
 
-    private val mSelectedUserInteractor =
-        SelectedUserInteractor(
-            FakeUserRepository(),
-            FakeFeatureFlagsClassic().apply { set(Flags.REFACTOR_GETCURRENTUSER, true) }
-        )
+    private val mSelectedUserInteractor = SelectedUserInteractor(FakeUserRepository())
 
     // Override the fromPrimaryBouncerTransitionInteractor provider from the superclass so our
     // underTest interactor is provided to any classes that need it.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
index 60fe7d2..140e919 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorTest.kt
@@ -2,9 +2,8 @@
 
 import android.content.pm.UserInfo
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_REFACTOR_GET_CURRENT_USER
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.runBlocking
@@ -24,15 +23,12 @@
     @Before
     fun setUp() {
         userRepository.setUserInfos(USER_INFOS)
-        underTest =
-            SelectedUserInteractor(
-                userRepository,
-                FakeFeatureFlagsClassic().apply { set(Flags.REFACTOR_GETCURRENTUSER, true) }
-            )
+        underTest = SelectedUserInteractor(userRepository)
     }
 
     @Test
     fun getSelectedUserIdReturnsId() {
+        mSetFlagsRule.enableFlags(FLAG_REFACTOR_GET_CURRENT_USER)
         runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) }
 
         val actualId = underTest.getSelectedUserId()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
index 3d8ae1e..3cabf0c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
@@ -27,8 +27,6 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -57,12 +55,6 @@
         keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(),
         bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
         keyguardUpdateMonitor: KeyguardUpdateMonitor = mock(KeyguardUpdateMonitor::class.java),
-        featureFlags: FakeFeatureFlagsClassic =
-            FakeFeatureFlagsClassic().apply {
-                set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, true)
-                set(Flags.FULL_SCREEN_USER_SWITCHER, false)
-                set(Flags.REFACTOR_GETCURRENTUSER, true)
-            },
         powerRepository: FakePowerRepository = FakePowerRepository(),
         userRepository: FakeUserRepository = FakeUserRepository(),
     ): WithDependencies {
@@ -98,8 +90,7 @@
             PowerInteractorFactory.create(
                 repository = powerRepository,
             )
-        val selectedUserInteractor =
-            SelectedUserInteractor(repository = userRepository, flags = featureFlags)
+        val selectedUserInteractor = SelectedUserInteractor(repository = userRepository)
         return WithDependencies(
             trustRepository = trustRepository,
             keyguardRepository = keyguardRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index d78bcb9..0b41926 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -387,7 +387,7 @@
     }
 
     fun selectedUserInteractor(): SelectedUserInteractor {
-        return SelectedUserInteractor(userRepository, featureFlags)
+        return SelectedUserInteractor(userRepository)
     }
 
     fun bouncerActionButtonInteractor(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt
index 427f92a..89672f1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/SelectedUserInteractorKosmos.kt
@@ -16,9 +16,7 @@
 
 package com.android.systemui.user.domain.interactor
 
-import com.android.systemui.flags.featureFlagsClassic
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.user.data.repository.userRepository
 
-val Kosmos.selectedUserInteractor by
-    Kosmos.Fixture { SelectedUserInteractor(userRepository, featureFlagsClassic) }
+val Kosmos.selectedUserInteractor by Kosmos.Fixture { SelectedUserInteractor(userRepository) }
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 77a5e3d..a4b2896 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -72,6 +72,7 @@
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
@@ -559,10 +560,11 @@
                 onClickIntent = UnlaunchableAppActivity.createInQuietModeDialogIntent(appUserId);
             } else if (provider.maskedBySuspendedPackage) {
                 showBadge = mUserManager.hasBadge(appUserId);
-                final String suspendingPackage = mPackageManagerInternal.getSuspendingPackage(
+                final UserPackage suspendingPackage = mPackageManagerInternal.getSuspendingPackage(
                         appInfo.packageName, appUserId);
                 // TODO(b/281839596): don't rely on platform always meaning suspended by admin.
-                if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
+                if (suspendingPackage != null
+                        && PLATFORM_PACKAGE_NAME.equals(suspendingPackage.packageName)) {
                     onClickIntent = mDevicePolicyManagerInternal.createShowAdminSupportIntent(
                             appUserId, true);
                 } else {
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 136692e..cac2efb 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -289,11 +289,11 @@
      *
      * @param suspendedPackage The package that has been suspended.
      * @param userId The user for which to check.
-     * @return Name of the package that suspended the given package. Returns {@code null} if the
-     * given package is not currently suspended and the platform package name - i.e.
-     * {@code "android"} - if the package was suspended by a device admin.
+     * @return User id and package name of the package that suspended the given package. Returns
+     * {@code null} if the given package is not currently suspended and the platform package name
+     * - i.e. {@code "android"} - if the package was suspended by a device admin.
      */
-    public abstract String getSuspendingPackage(String suspendedPackage, int userId);
+    public abstract UserPackage getSuspendingPackage(String suspendedPackage, int userId);
 
     /**
      * Suspend or unsuspend packages upon admin request.
@@ -312,13 +312,13 @@
      * suspended application.
      *
      * @param suspendedPackage The package that has been suspended.
-     * @param suspendingPackage
+     * @param suspendingPackage The package responsible for suspension.
      * @param userId The user for which to check.
      * @return A {@link SuspendDialogInfo} object describing the dialog to be shown.
      */
     @Nullable
     public abstract SuspendDialogInfo getSuspendedDialogInfo(String suspendedPackage,
-            String suspendingPackage, int userId);
+            UserPackage suspendingPackage, int userId);
 
     /**
      * Gets any distraction flags set via
@@ -1168,14 +1168,14 @@
     public abstract void clearBlockUninstallForUser(@UserIdInt int userId);
 
     /**
-     * Unsuspends all packages suspended by the given package for the user.
+     * Unsuspends all packages suspended by an admin for the user.
      */
-    public abstract void unsuspendForSuspendingPackage(String suspendingPackage, int userId);
+    public abstract void unsuspendAdminSuspendedPackages(int userId);
 
     /**
-     * Returns {@code true} if the package is suspending any packages for the user.
+     * Returns {@code true} if an admin is suspending any packages for the user.
      */
-    public abstract boolean isSuspendingAnyPackages(String suspendingPackage, int userId);
+    public abstract boolean isAdminSuspendingAnyPackages(int userId);
 
     /**
      * Register to listen for loading progress of an installed package.
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index c440a64..16e043c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -6499,10 +6499,12 @@
                                 nextEnabledImes).getId();
 
                         // Reset enabled IMEs.
-                        settings.putEnabledInputMethodsStr("");
-                        nextEnabledImes.forEach(
-                                imi -> settings.appendAndPutEnabledInputMethodLocked(
-                                        imi.getId()));
+                        final String[] nextEnabledImeIds = new String[nextEnabledImes.size()];
+                        for (int i = 0; i < nextEnabledImeIds.length; ++i) {
+                            nextEnabledImeIds[i] = nextEnabledImes.get(i).getId();
+                        }
+                        settings.putEnabledInputMethodsStr(InputMethodUtils.concatEnabledImeIds(
+                                settings.getEnabledInputMethodsStr(), nextEnabledImeIds));
 
                         // Reset selected IME.
                         settings.putSelectedInputMethod(nextIme);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 2128356..773293f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -33,6 +33,7 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Pair;
 import android.util.Printer;
@@ -50,6 +51,8 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.StringJoiner;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
@@ -952,24 +955,64 @@
         final String enabledInputMethodsStr = TextUtils.nullIfEmpty(
                 SecureSettingsWrapper.getString(Settings.Secure.ENABLED_INPUT_METHODS, null,
                         userId));
-        if (enabledInputMethodsStr == null) {
-            return List.of();
+        final ArrayList<String> result = new ArrayList<>();
+        splitEnabledImeStr(enabledInputMethodsStr, result::add);
+        return result;
+    }
+
+    /**
+     * Split enabled IME string ({@link Settings.Secure#ENABLED_INPUT_METHODS}) into IME IDs.
+     *
+     * @param text a text formatted with {@link Settings.Secure#ENABLED_INPUT_METHODS}.
+     * @param consumer {@link Consumer} called back when a new IME ID is found.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    static void splitEnabledImeStr(@Nullable String text, @NonNull Consumer<String> consumer) {
+        if (TextUtils.isEmpty(text)) {
+            return;
         }
         final TextUtils.SimpleStringSplitter inputMethodSplitter =
                 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
         final TextUtils.SimpleStringSplitter subtypeSplitter =
                 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
-        inputMethodSplitter.setString(enabledInputMethodsStr);
-        final ArrayList<String> result = new ArrayList<>();
+        inputMethodSplitter.setString(text);
         while (inputMethodSplitter.hasNext()) {
             String nextImsStr = inputMethodSplitter.next();
             subtypeSplitter.setString(nextImsStr);
             if (subtypeSplitter.hasNext()) {
                 // The first element is ime id.
-                result.add(subtypeSplitter.next());
+                consumer.accept(subtypeSplitter.next());
             }
         }
-        return result;
+    }
+
+    /**
+     * Concat given IME IDs with an existing enabled IME
+     * ({@link Settings.Secure#ENABLED_INPUT_METHODS}).
+     *
+     * @param existingEnabledImeId an existing {@link Settings.Secure#ENABLED_INPUT_METHODS} to
+     *                             which {@code imeIDs} will be added.
+     * @param imeIds an array of IME IDs to be added. For IME IDs that are already seen in
+     *               {@code existingEnabledImeId} will be skipped.
+     * @return a new enabled IME ID string that can be stored in
+     *         {@link Settings.Secure#ENABLED_INPUT_METHODS}.
+     */
+    @NonNull
+    static String concatEnabledImeIds(@NonNull String existingEnabledImeId,
+            @NonNull String... imeIds) {
+        final ArraySet<String> alreadyEnabledIds = new ArraySet<>();
+        final StringJoiner joiner = new StringJoiner(Character.toString(INPUT_METHOD_SEPARATOR));
+        if (!TextUtils.isEmpty(existingEnabledImeId)) {
+            splitEnabledImeStr(existingEnabledImeId, alreadyEnabledIds::add);
+            joiner.add(existingEnabledImeId);
+        }
+        for (String id : imeIds) {
+            if (!alreadyEnabledIds.contains(id)) {
+                joiner.add(id);
+                alreadyEnabledIds.add(id);
+            }
+        }
+        return joiner.toString();
     }
 
     /**
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index b12180b..4b8de4e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -19,6 +19,7 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
+import android.service.notification.DeviceEffectsApplier;
 
 import java.util.Set;
 
@@ -54,4 +55,22 @@
     void cleanupHistoryFiles();
 
     void removeBitmaps();
+
+    /**
+     * Sets the {@link DeviceEffectsApplier} that will be used to apply the different
+     * {@link android.service.notification.ZenDeviceEffects} that are relevant for the platform
+     * when {@link android.service.notification.ZenModeConfig.ZenRule} instances are activated and
+     * deactivated.
+     *
+     * <p>This method is optional and needs only be called if the platform supports non-standard
+     * effects (i.e. any that are not <em>public APIs</em> in
+     * {@link android.service.notification.ZenDeviceEffects}, or if they must be applied in a
+     * non-standard fashion. If not used, a {@link DefaultDeviceEffectsApplier} will be invoked,
+     * which should be sufficient for most devices.
+     *
+     * <p>If this method is called, it <em>must</em> be during system startup and <em>before</em>
+     * the {@link com.android.server.SystemService#PHASE_THIRD_PARTY_APPS_CAN_START} boot phase.
+     * Otherwise an {@link IllegalStateException} will be thrown.
+     */
+    void setDeviceEffectsApplier(DeviceEffectsApplier applier);
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 66a9740..e7ae610 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -251,6 +251,7 @@
 import android.service.notification.Adjustment;
 import android.service.notification.Condition;
 import android.service.notification.ConversationChannelWrapper;
+import android.service.notification.DeviceEffectsApplier;
 import android.service.notification.IConditionProvider;
 import android.service.notification.INotificationListener;
 import android.service.notification.IStatusBarNotificationHolder;
@@ -6964,6 +6965,18 @@
                 }
             }
         }
+
+        @Override
+        public void setDeviceEffectsApplier(DeviceEffectsApplier applier) {
+            if (!android.app.Flags.modesApi()) {
+                return;
+            }
+            if (mZenModeHelper == null) {
+                throw new IllegalStateException("ZenModeHelper is not yet ready!");
+            }
+            // This can also throw IllegalStateException if called too late.
+            mZenModeHelper.setDeviceEffectsApplier(applier);
+        }
     };
 
     private static boolean isBigPictureWithBitmapOrIcon(Notification n) {
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index 27f4e11..482807c 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -518,7 +518,9 @@
     boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId)
             throws PackageManager.NameNotFoundException;
 
-    boolean isSuspendingAnyPackages(@NonNull String suspendingPackage, @UserIdInt int userId);
+    /** Check if the package is suspending any package. */
+    boolean isSuspendingAnyPackages(@NonNull String suspendingPackage,
+            @UserIdInt int suspendingUserId, int targetUserId);
 
     @NonNull
     ParceledListSlice<IntentFilter> getAllIntentFilters(@NonNull String packageName);
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index abfd571..3cb2420 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -96,6 +96,7 @@
 import android.content.pm.SigningDetails;
 import android.content.pm.SigningInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.content.pm.VersionedPackage;
 import android.os.Binder;
 import android.os.Build;
@@ -3864,19 +3865,15 @@
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
-
-                var usingSharedLibraryPair =
-                        getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId);
                 SharedLibraryInfo resLibInfo = new SharedLibraryInfo(libInfo.getPath(),
                         libInfo.getPackageName(), libInfo.getAllCodePaths(),
                         libInfo.getName(), libInfo.getLongVersion(),
                         libInfo.getType(), declaringPackage,
-                        usingSharedLibraryPair.first,
                         (libInfo.getDependencies() == null
                                 ? null
                                 : new ArrayList<>(libInfo.getDependencies())),
-                        libInfo.isNative());
-
+                        libInfo.isNative(),
+                        getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId));
                 if (result == null) {
                     result = new ArrayList<>();
                 }
@@ -5012,11 +5009,13 @@
 
     @Override
     public boolean isSuspendingAnyPackages(@NonNull String suspendingPackage,
-            @UserIdInt int userId) {
+            @UserIdInt int suspendingUserId, int targetUserId) {
+        final UserPackage suspender = UserPackage.of(suspendingUserId, suspendingPackage);
         for (final PackageStateInternal packageState : getPackageStates().values()) {
-            final PackageUserStateInternal state = packageState.getUserStateOrDefault(userId);
+            final PackageUserStateInternal state =
+                    packageState.getUserStateOrDefault(targetUserId);
             if (state.getSuspendParams() != null
-                    && state.getSuspendParams().containsKey(suspendingPackage)) {
+                    && state.getSuspendParams().containsKey(suspender)) {
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index ba66377..127bf49 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -822,7 +822,7 @@
             return new LauncherActivityInfoInternal(
                     activityInfo,
                     new IncrementalStatesInfo(
-                            false /* isLoading */, 1 /* progress */, 0 /* loadingCompletedTime */),
+                            false /* isLoading */, 0 /* progress */, 0 /* loadingCompletedTime */),
                     user);
         }
 
@@ -1599,6 +1599,33 @@
         }
 
         @Override
+        public @Nullable IntentSender getAppMarketActivityIntent(@NonNull String callingPackage,
+                @Nullable String packageName, @NonNull UserHandle user) {
+            // Only system launchers, which have access to recents should have access to this API.
+            // TODO(b/303803157): Update access control for this API to default Launcher app.
+            if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) {
+                throw new SecurityException("Caller is not the recents app");
+            }
+            if (!canAccessProfile(user.getIdentifier(),
+                    "Can't access AppMarketActivity for another user")) {
+                return null;
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                // TODO(b/316118005): Add code to launch the app installer for the packageName.
+                Intent appMarketIntent = new Intent(Intent.ACTION_MAIN);
+                appMarketIntent.addCategory(Intent.CATEGORY_APP_MARKET);
+                final PendingIntent pi = PendingIntent.getActivityAsUser(
+                        mContext, /* requestCode */ 0, appMarketIntent, PendingIntent.FLAG_ONE_SHOT
+                                | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT,
+                        /* options */ null, user);
+                return pi == null ? null : pi.getIntentSender();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void startActivityAsUser(IApplicationThread caller, String callingPackage,
                 String callingFeatureId, ComponentName component, Rect sourceBounds,
                 Bundle opts, UserHandle user) throws RemoteException {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 7d6dd62..2942bbb 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -821,6 +821,18 @@
             params.installFlags &= ~PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK;
         }
 
+        params.installFlags &= ~PackageManager.INSTALL_UNARCHIVE;
+        if (Flags.archiving() && params.appPackageName != null) {
+            PackageStateInternal ps = mPm.snapshotComputer().getPackageStateInternal(
+                    params.appPackageName, SYSTEM_UID);
+            if (ps != null
+                    && PackageArchiver.isArchived(ps.getUserStateOrDefault(userId))
+                    && PackageArchiver.getResponsibleInstallerPackage(ps)
+                            .equals(requestedInstallerPackageName)) {
+                params.installFlags |= PackageManager.INSTALL_UNARCHIVE;
+            }
+        }
+
         if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0
                 && !PackageManagerServiceUtils.isSystemOrRootOrShell(callingUid)
                 && (snapshot.getFlagsForUid(callingUid) & ApplicationInfo.FLAG_SYSTEM)
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index c737b45..8da1683 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -19,6 +19,8 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 import static android.content.pm.PackageManager.RESTRICTION_NONE;
 
+import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -37,6 +39,7 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.SuspendDialogInfo;
+import android.content.pm.UserPackage;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -250,7 +253,7 @@
         getSuspendPackageHelper().removeSuspensionsBySuspendingPackage(snapshot(),
                 new String[]{packageName},
                 (suspendingPackage) -> !PackageManagerService.PLATFORM_PACKAGE_NAME.equals(
-                        suspendingPackage),
+                        suspendingPackage.packageName),
                 userId);
     }
 
@@ -269,7 +272,7 @@
 
     @Override
     @Deprecated
-    public final String getSuspendingPackage(String suspendedPackage, int userId) {
+    public final UserPackage getSuspendingPackage(String suspendedPackage, int userId) {
         return getSuspendPackageHelper().getSuspendingPackage(snapshot(), suspendedPackage, userId,
                 Binder.getCallingUid());
     }
@@ -277,7 +280,7 @@
     @Override
     @Deprecated
     public final SuspendDialogInfo getSuspendedDialogInfo(String suspendedPackage,
-            String suspendingPackage, int userId) {
+            UserPackage suspendingPackage, int userId) {
         return getSuspendPackageHelper().getSuspendedDialogInfo(snapshot(), suspendedPackage,
                 suspendingPackage, userId, Binder.getCallingUid());
     }
@@ -683,14 +686,16 @@
 
     @Override
     @Deprecated
-    public final void unsuspendForSuspendingPackage(final String packageName, int affectedUser) {
-        mService.unsuspendForSuspendingPackage(snapshot(), packageName, affectedUser);
+    public final void unsuspendAdminSuspendedPackages(int affectedUser) {
+        final int suspendingUserId = affectedUser;
+        mService.unsuspendForSuspendingPackage(snapshot(), PLATFORM_PACKAGE_NAME, suspendingUserId);
     }
 
     @Override
     @Deprecated
-    public final boolean isSuspendingAnyPackages(String suspendingPackage, int userId) {
-        return snapshot().isSuspendingAnyPackages(suspendingPackage, userId);
+    public final boolean isAdminSuspendingAnyPackages(int userId) {
+        final int suspendingUserId = userId;
+        return snapshot().isSuspendingAnyPackages(PLATFORM_PACKAGE_NAME, suspendingUserId, userId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 56365b6..bc441b8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -292,6 +292,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /**
  * Keep track of all those APKs everywhere.
@@ -3137,7 +3138,7 @@
     }
 
     private void enforceCanSetPackagesSuspendedAsUser(@NonNull Computer snapshot,
-            boolean quarantined, String callingPackage, int callingUid, int userId,
+            boolean quarantined, UserPackage suspender, int callingUid, int targetUserId,
             String callingMethod) {
         if (callingUid == Process.ROOT_UID
                 // Need to compare app-id to allow system dialogs access on secondary users
@@ -3145,9 +3146,10 @@
             return;
         }
 
-        final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId);
+        final String ownerPackage =
+                mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(targetUserId);
         if (ownerPackage != null) {
-            final int ownerUid = snapshot.getPackageUid(ownerPackage, 0, userId);
+            final int ownerUid = snapshot.getPackageUid(ownerPackage, 0, targetUserId);
             if (ownerUid == callingUid) {
                 return;
             }
@@ -3168,25 +3170,27 @@
                     callingMethod);
         }
 
-        final int packageUid = snapshot.getPackageUid(callingPackage, 0, userId);
+        final int packageUid = snapshot.getPackageUid(suspender.packageName, 0, targetUserId);
         final boolean allowedPackageUid = packageUid == callingUid;
         // TODO(b/139383163): remove special casing for shell and enforce INTERACT_ACROSS_USERS_FULL
         final boolean allowedShell = callingUid == SHELL_UID
                 && UserHandle.isSameApp(packageUid, callingUid);
 
         if (!allowedShell && !allowedPackageUid) {
-            throw new SecurityException("Calling package " + callingPackage + " in user "
-                    + userId + " does not belong to calling uid " + callingUid);
+            throw new SecurityException("Suspending package " + suspender.packageName
+                    + " in user " + targetUserId + " does not belong to calling uid " + callingUid);
         }
     }
 
     void unsuspendForSuspendingPackage(@NonNull Computer computer, String suspendingPackage,
-            @UserIdInt int userId) {
+            @UserIdInt int suspendingUserId) {
         // TODO: This can be replaced by a special parameter to iterate all packages, rather than
         //  this weird pre-collect of all packages.
         final String[] allPackages = computer.getPackageStates().keySet().toArray(new String[0]);
+        final Predicate<UserPackage> suspenderPredicate =
+                UserPackage.of(suspendingUserId, suspendingPackage)::equals;
         mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(computer,
-                allPackages, suspendingPackage::equals, userId);
+                allPackages, suspenderPredicate, suspendingUserId);
     }
 
     void removeAllDistractingPackageRestrictions(@NonNull Computer snapshot, int userId) {
@@ -5259,8 +5263,9 @@
                 if (!snapshot.isPackageSuspendedForUser(packageName, userId)) {
                     return null;
                 }
-                return mSuspendPackageHelper.getSuspendingPackage(snapshot, packageName, userId,
-                        callingUid);
+                final UserPackage suspender = mSuspendPackageHelper.getSuspendingPackage(
+                        snapshot, packageName, userId, callingUid);
+                return suspender != null ? suspender.packageName : null;
             } catch (PackageManager.NameNotFoundException e) {
                 return null;
             }
@@ -6198,7 +6203,8 @@
         @Override
         public String[] setPackagesSuspendedAsUser(String[] packageNames, boolean suspended,
                 PersistableBundle appExtras, PersistableBundle launcherExtras,
-                SuspendDialogInfo dialogInfo, int flags, String callingPackage, int userId) {
+                SuspendDialogInfo dialogInfo, int flags, String suspendingPackage,
+                int suspendingUserId, int targetUserId) {
             final int callingUid = Binder.getCallingUid();
             boolean quarantined = false;
             if (Flags.quarantinedEnabled()) {
@@ -6207,14 +6213,15 @@
                 } else if (FeatureFlagUtils.isEnabled(mContext,
                         SETTINGS_TREAT_PAUSE_AS_QUARANTINE)) {
                     final String wellbeingPkg = mContext.getString(R.string.config_systemWellbeing);
-                    quarantined = callingPackage.equals(wellbeingPkg);
+                    quarantined = suspendingPackage.equals(wellbeingPkg);
                 }
             }
             final Computer snapshot = snapshotComputer();
-            enforceCanSetPackagesSuspendedAsUser(snapshot, quarantined, callingPackage, callingUid,
-                    userId, "setPackagesSuspendedAsUser");
+            final UserPackage suspender = UserPackage.of(targetUserId, suspendingPackage);
+            enforceCanSetPackagesSuspendedAsUser(snapshot, quarantined, suspender, callingUid,
+                    targetUserId, "setPackagesSuspendedAsUser");
             return mSuspendPackageHelper.setPackagesSuspended(snapshot, packageNames, suspended,
-                    appExtras, launcherExtras, dialogInfo, callingPackage, userId, callingUid,
+                    appExtras, launcherExtras, dialogInfo, suspender, targetUserId, callingUid,
                     quarantined);
         }
 
@@ -6653,7 +6660,7 @@
             final Computer computer = snapshotComputer();
             final String[] allPackages = computer.getAllAvailablePackageNames();
             mSuspendPackageHelper.removeSuspensionsBySuspendingPackage(computer, allPackages,
-                    (suspendingPackage) -> !PLATFORM_PACKAGE_NAME.equals(suspendingPackage),
+                    (suspender) -> !PLATFORM_PACKAGE_NAME.equals(suspender.packageName),
                     userId);
         }
 
@@ -6667,8 +6674,13 @@
         @Override
         public String[] setPackagesSuspendedByAdmin(
                 @UserIdInt int userId, @NonNull String[] packageNames, boolean suspended) {
-            return mSuspendPackageHelper.setPackagesSuspendedByAdmin(
-                    snapshotComputer(), userId, packageNames, suspended);
+            final int suspendingUserId = userId;
+            final UserPackage suspender = UserPackage.of(
+                    suspendingUserId, PackageManagerService.PLATFORM_PACKAGE_NAME);
+            return mSuspendPackageHelper.setPackagesSuspended(snapshotComputer(), packageNames,
+                    suspended, null /* appExtras */, null /* launcherExtras */,
+                    null /* dialogInfo */, suspender, userId, Process.SYSTEM_UID,
+                    false /* quarantined */);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 322557b..243fb16 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2827,7 +2827,7 @@
             mInterface.setPackagesSuspendedAsUser(packageNames.toArray(new String[] {}),
                     suspendedState, ((appExtras.size() > 0) ? appExtras : null),
                     ((launcherExtras.size() > 0) ? launcherExtras : null),
-                    info, flags, callingPackage, translatedUserId);
+                    info, flags, callingPackage, UserHandle.USER_SYSTEM, translatedUserId);
             for (int i = 0; i < packageNames.size(); i++) {
                 final String packageName = packageNames.get(i);
                 pw.println("Package " + packageName + " new suspended state: "
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 7d0a1f6..28a90f3 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -32,6 +32,7 @@
 import android.content.pm.SigningDetails;
 import android.content.pm.SigningInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.content.pm.overlay.OverlayPaths;
 import android.os.UserHandle;
 import android.os.incremental.IncrementalManager;
@@ -952,7 +953,7 @@
 
     void setUserState(int userId, long ceDataInode, long deDataInode, int enabled,
                       boolean installed, boolean stopped, boolean notLaunched, boolean hidden,
-                      int distractionFlags, ArrayMap<String, SuspendParams> suspendParams,
+                      int distractionFlags, ArrayMap<UserPackage, SuspendParams> suspendParams,
                       boolean instantApp, boolean virtualPreload, String lastDisableAppCaller,
                       ArraySet<String> enabledComponents, ArraySet<String> disabledComponents,
                       int installReason, int uninstallReason,
@@ -1182,7 +1183,7 @@
             if (state.isSuspended()) {
                 for (int j = 0; j < state.getSuspendParams().size(); j++) {
                     proto.write(PackageProto.UserInfoProto.SUSPENDING_PACKAGE,
-                            state.getSuspendParams().keyAt(j));
+                            state.getSuspendParams().keyAt(j).packageName);
                 }
             }
             proto.write(PackageProto.UserInfoProto.IS_STOPPED, state.isStopped());
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index cfbaae3..d695226 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -51,6 +51,7 @@
 import android.content.pm.Signature;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.overlay.OverlayPaths;
 import android.net.Uri;
@@ -1956,7 +1957,7 @@
                         ArchiveState archiveState = null;
 
                         int packageDepth = parser.getDepth();
-                        ArrayMap<String, SuspendParams> suspendParamsMap = null;
+                        ArrayMap<UserPackage, SuspendParams> suspendParamsMap = null;
                         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                                 && (type != XmlPullParser.END_TAG
                                 || parser.getDepth() > packageDepth)) {
@@ -1983,18 +1984,15 @@
                                             parser);
                                     break;
                                 case TAG_SUSPEND_PARAMS:
-                                    final String suspendingPackage = parser.getAttributeValue(null,
-                                            ATTR_SUSPENDING_PACKAGE);
-                                    if (suspendingPackage == null) {
-                                        Slog.wtf(TAG, "No suspendingPackage found inside tag "
-                                                + TAG_SUSPEND_PARAMS);
+                                    Map.Entry<UserPackage, SuspendParams> entry =
+                                            readSuspensionParamsLPr(userId, parser);
+                                    if (entry == null) {
                                         continue;
                                     }
                                     if (suspendParamsMap == null) {
                                         suspendParamsMap = new ArrayMap<>();
                                     }
-                                    suspendParamsMap.put(suspendingPackage,
-                                            SuspendParams.restoreFromXml(parser));
+                                    suspendParamsMap.put(entry.getKey(), entry.getValue());
                                     break;
                                 case TAG_ARCHIVE_STATE:
                                     archiveState = parseArchiveState(parser);
@@ -2016,7 +2014,8 @@
                                     oldSuspendedLauncherExtras,
                                     false /* quarantined */);
                             suspendParamsMap = new ArrayMap<>();
-                            suspendParamsMap.put(oldSuspendingPackage, suspendParams);
+                            suspendParamsMap.put(
+                                    UserPackage.of(userId, oldSuspendingPackage), suspendParams);
                         }
 
                         if (blockUninstall) {
@@ -2058,6 +2057,20 @@
         }
     }
 
+    @Nullable
+    private static Map.Entry<UserPackage, SuspendParams> readSuspensionParamsLPr(
+            int userId, TypedXmlPullParser parser) throws IOException {
+        final String suspendingPackage = parser.getAttributeValue(null, ATTR_SUSPENDING_PACKAGE);
+        if (suspendingPackage == null) {
+            Slog.wtf(TAG, "No suspendingPackage found inside tag " + TAG_SUSPEND_PARAMS);
+            return null;
+        }
+        final int suspendingUserId = userId;
+        return Map.entry(
+                UserPackage.of(suspendingUserId, suspendingPackage),
+                SuspendParams.restoreFromXml(parser));
+    }
+
     private static ArchiveState parseArchiveState(TypedXmlPullParser parser)
             throws XmlPullParserException, IOException {
         String installerTitle = parser.getAttributeValue(null,
@@ -2414,10 +2427,11 @@
                         }
                         if (ustate.isSuspended()) {
                             for (int i = 0; i < ustate.getSuspendParams().size(); i++) {
-                                final String suspendingPackage = ustate.getSuspendParams().keyAt(i);
+                                final UserPackage suspendingPackage =
+                                        ustate.getSuspendParams().keyAt(i);
                                 serializer.startTag(null, TAG_SUSPEND_PARAMS);
                                 serializer.attribute(null, ATTR_SUSPENDING_PACKAGE,
-                                        suspendingPackage);
+                                        suspendingPackage.packageName);
                                 final SuspendParams params =
                                         ustate.getSuspendParams().valueAt(i);
                                 if (params != null) {
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index c2a960a..4e70cc5 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -27,10 +27,10 @@
 import android.app.AppOpsManager;
 import android.content.Intent;
 import android.content.pm.SuspendDialogInfo;
+import android.content.pm.UserPackage;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.PersistableBundle;
-import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArrayMap;
@@ -88,8 +88,8 @@
      * @param dialogInfo An optional {@link SuspendDialogInfo} object describing the dialog that
      *                   should be shown to the user when they try to launch a suspended app.
      *                   Ignored if {@code suspended} is false.
-     * @param callingPackage The caller's package name.
-     * @param userId The user where packages reside.
+     * @param suspendingPackage The caller's package name.
+     * @param targetUserId The user where packages reside.
      * @param callingUid The caller's uid.
      * @return The names of failed packages.
      */
@@ -97,14 +97,14 @@
     String[] setPackagesSuspended(@NonNull Computer snapshot, @Nullable String[] packageNames,
             boolean suspended, @Nullable PersistableBundle appExtras,
             @Nullable PersistableBundle launcherExtras, @Nullable SuspendDialogInfo dialogInfo,
-            @NonNull String callingPackage, @UserIdInt int userId, int callingUid,
+            @NonNull UserPackage suspendingPackage, @UserIdInt int targetUserId, int callingUid,
             boolean quarantined) {
         if (ArrayUtils.isEmpty(packageNames)) {
             return packageNames;
         }
-        if (suspended && !quarantined && !isSuspendAllowedForUser(snapshot, userId,
-                callingUid)) {
-            Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
+        if (suspended && !quarantined
+                && !isSuspendAllowedForUser(snapshot, targetUserId, callingUid)) {
+            Slog.w(TAG, "Cannot suspend due to restrictions on user " + targetUserId);
             return packageNames;
         }
 
@@ -119,19 +119,21 @@
         final IntArray changedUids = new IntArray(packageNames.length);
 
         final boolean[] canSuspend = suspended
-                ? canSuspendPackageForUser(snapshot, packageNames, userId, callingUid)
+                ? canSuspendPackageForUser(snapshot, packageNames, targetUserId, callingUid)
                 : null;
         for (int i = 0; i < packageNames.length; i++) {
             final String packageName = packageNames[i];
-            if (callingPackage.equals(packageName)) {
-                Slog.w(TAG, "Calling package: " + callingPackage + " trying to "
+            if (suspendingPackage.packageName.equals(packageName)
+                    && suspendingPackage.userId == targetUserId) {
+                Slog.w(TAG, "Suspending package: " + suspendingPackage + " trying to "
                         + (suspended ? "" : "un") + "suspend itself. Ignoring");
                 unmodifiablePackages.add(packageName);
                 continue;
             }
             final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
-            if (packageState == null || !packageState.getUserStateOrDefault(userId).isInstalled()
-                    || snapshot.shouldFilterApplication(packageState, callingUid, userId)) {
+            if (packageState == null
+                    || !packageState.getUserStateOrDefault(targetUserId).isInstalled()
+                    || snapshot.shouldFilterApplication(packageState, callingUid, targetUserId)) {
                 Slog.w(TAG, "Could not find package setting for package: " + packageName
                         + ". Skipping suspending/un-suspending.");
                 unmodifiablePackages.add(packageName);
@@ -142,34 +144,34 @@
                 continue;
             }
 
-            final WatchedArrayMap<String, SuspendParams> suspendParamsMap =
-                    packageState.getUserStateOrDefault(userId).getSuspendParams();
+            final WatchedArrayMap<UserPackage, SuspendParams> suspendParamsMap =
+                    packageState.getUserStateOrDefault(targetUserId).getSuspendParams();
             final SuspendParams oldSuspendParams = suspendParamsMap == null
-                    ? null : suspendParamsMap.get(callingPackage);
+                    ? null : suspendParamsMap.get(suspendingPackage);
             boolean changed = !Objects.equals(oldSuspendParams, newSuspendParams);
 
             if (suspended && !changed) {
                 // Carried over API behavior, must notify change even if no change
                 notifyPackagesList.add(packageName);
                 notifyUids.add(
-                        UserHandle.getUid(userId, packageState.getAppId()));
+                        UserHandle.getUid(targetUserId, packageState.getAppId()));
                 continue;
             }
 
-            // If only the callingPackage is suspending this package,
+            // If only the suspendingPackage is suspending this package,
             // it will be unsuspended when this change is committed
             boolean packageUnsuspended = !suspended
                     && CollectionUtils.size(suspendParamsMap) == 1
-                    && suspendParamsMap.containsKey(callingPackage);
+                    && suspendParamsMap.containsKey(suspendingPackage);
             if (suspended || packageUnsuspended) {
                 // Always notify of a suspend call + notify when fully unsuspended
                 notifyPackagesList.add(packageName);
-                notifyUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+                notifyUids.add(UserHandle.getUid(targetUserId, packageState.getAppId()));
             }
 
             if (changed) {
                 changedPackagesList.add(packageName);
-                changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+                changedUids.add(UserHandle.getUid(targetUserId, packageState.getAppId()));
             } else {
                 Slog.w(TAG, "No change is needed for package: " + packageName
                         + ". Skipping suspending/un-suspending.");
@@ -181,11 +183,11 @@
             for (int index = 0; index < size; index++) {
                 final String packageName  = changedPackagesList.valueAt(index);
                 final PackageUserStateWrite userState = mutator.forPackage(packageName)
-                        .userState(userId);
+                        .userState(targetUserId);
                 if (suspended) {
-                    userState.putSuspendParams(callingPackage, newSuspendParams);
+                    userState.putSuspendParams(suspendingPackage, newSuspendParams);
                 } else {
-                    userState.removeSuspension(callingPackage);
+                    userState.removeSuspension(suspendingPackage);
                 }
             }
         });
@@ -197,17 +199,17 @@
             mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
                     suspended ? Intent.ACTION_PACKAGES_SUSPENDED
                             : Intent.ACTION_PACKAGES_UNSUSPENDED,
-                    changedPackages, notifyUids.toArray(), quarantined, userId);
+                    changedPackages, notifyUids.toArray(), quarantined, targetUserId);
             mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, changedPackages,
-                    suspended, userId);
-            mPm.scheduleWritePackageRestrictions(userId);
+                    suspended, targetUserId);
+            mPm.scheduleWritePackageRestrictions(targetUserId);
         }
         // Send the suspension changed broadcast to ensure suspension state is not stale.
         if (!changedPackagesList.isEmpty()) {
             mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
                     Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
                     changedPackagesList.toArray(new String[0]), changedUids.toArray(), quarantined,
-                    userId);
+                    targetUserId);
         }
         return unmodifiablePackages.toArray(new String[0]);
     }
@@ -216,19 +218,19 @@
      * Returns the names in the {@code packageNames} which can not be suspended by the caller.
      *
      * @param packageNames The names of packages to check.
-     * @param userId The user where packages reside.
+     * @param targetUserId The user where packages reside.
      * @param callingUid The caller's uid.
      * @return The names of packages which are Unsuspendable.
      */
     @NonNull
     String[] getUnsuspendablePackagesForUser(@NonNull Computer snapshot,
-            @NonNull String[] packageNames, @UserIdInt int userId, int callingUid) {
-        if (!isSuspendAllowedForUser(snapshot, userId, callingUid)) {
-            Slog.w(TAG, "Cannot suspend due to restrictions on user " + userId);
+            @NonNull String[] packageNames, @UserIdInt int targetUserId, int callingUid) {
+        if (!isSuspendAllowedForUser(snapshot, targetUserId, callingUid)) {
+            Slog.w(TAG, "Cannot suspend due to restrictions on user " + targetUserId);
             return packageNames;
         }
         final ArraySet<String> unactionablePackages = new ArraySet<>();
-        final boolean[] canSuspend = canSuspendPackageForUser(snapshot, packageNames, userId,
+        final boolean[] canSuspend = canSuspendPackageForUser(snapshot, packageNames, targetUserId,
                 callingUid);
         for (int i = 0; i < packageNames.length; i++) {
             if (!canSuspend[i]) {
@@ -237,7 +239,7 @@
             }
             final PackageStateInternal packageState =
                     snapshot.getPackageStateForInstalledAndFiltered(
-                            packageNames[i], callingUid, userId);
+                            packageNames[i], callingUid, targetUserId);
             if (packageState == null) {
                 Slog.w(TAG, "Could not find package setting for package: " + packageNames[i]);
                 unactionablePackages.add(packageNames[i]);
@@ -285,30 +287,31 @@
      * @param packagesToChange The packages on which the suspension are to be removed.
      * @param suspendingPackagePredicate A predicate identifying the suspending packages whose
      *                                   suspensions will be removed.
-     * @param userId The user for which the changes are taking place.
+     * @param targetUserId The user for which the changes are taking place.
      */
     void removeSuspensionsBySuspendingPackage(@NonNull Computer snapshot,
             @NonNull String[] packagesToChange,
-            @NonNull Predicate<String> suspendingPackagePredicate, int userId) {
+            @NonNull Predicate<UserPackage> suspendingPackagePredicate, int targetUserId) {
         final List<String> unsuspendedPackages = new ArrayList<>();
         final IntArray unsuspendedUids = new IntArray();
-        final ArrayMap<String, ArraySet<String>> pkgToSuspendingPkgsToCommit = new ArrayMap<>();
+        final ArrayMap<String, ArraySet<UserPackage>> pkgToSuspendingPkgsToCommit =
+                new ArrayMap<>();
         for (String packageName : packagesToChange) {
             final PackageStateInternal packageState =
                     snapshot.getPackageStateInternal(packageName);
             final PackageUserStateInternal packageUserState = packageState == null
-                    ? null : packageState.getUserStateOrDefault(userId);
+                    ? null : packageState.getUserStateOrDefault(targetUserId);
             if (packageUserState == null || !packageUserState.isSuspended()) {
                 continue;
             }
 
-            WatchedArrayMap<String, SuspendParams> suspendParamsMap =
+            WatchedArrayMap<UserPackage, SuspendParams> suspendParamsMap =
                     packageUserState.getSuspendParams();
             int countRemoved = 0;
             for (int index = 0; index < suspendParamsMap.size(); index++) {
-                String suspendingPackage = suspendParamsMap.keyAt(index);
+                UserPackage suspendingPackage = suspendParamsMap.keyAt(index);
                 if (suspendingPackagePredicate.test(suspendingPackage)) {
-                    ArraySet<String> suspendingPkgsToCommit =
+                    ArraySet<UserPackage> suspendingPkgsToCommit =
                             pkgToSuspendingPkgsToCommit.get(packageName);
                     if (suspendingPkgsToCommit == null) {
                         suspendingPkgsToCommit = new ArraySet<>();
@@ -322,31 +325,33 @@
             // Everything would be removed and package unsuspended
             if (countRemoved == suspendParamsMap.size()) {
                 unsuspendedPackages.add(packageState.getPackageName());
-                unsuspendedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+                unsuspendedUids.add(UserHandle.getUid(targetUserId, packageState.getAppId()));
             }
         }
 
         mPm.commitPackageStateMutation(null, mutator -> {
             for (int mapIndex = 0; mapIndex < pkgToSuspendingPkgsToCommit.size(); mapIndex++) {
                 String packageName = pkgToSuspendingPkgsToCommit.keyAt(mapIndex);
-                ArraySet<String> packagesToRemove = pkgToSuspendingPkgsToCommit.valueAt(mapIndex);
-                PackageUserStateWrite userState = mutator.forPackage(packageName).userState(userId);
+                ArraySet<UserPackage> packagesToRemove =
+                        pkgToSuspendingPkgsToCommit.valueAt(mapIndex);
+                PackageUserStateWrite userState =
+                        mutator.forPackage(packageName).userState(targetUserId);
                 for (int setIndex = 0; setIndex < packagesToRemove.size(); setIndex++) {
                     userState.removeSuspension(packagesToRemove.valueAt(setIndex));
                 }
             }
         });
 
-        mPm.scheduleWritePackageRestrictions(userId);
+        mPm.scheduleWritePackageRestrictions(targetUserId);
         final Computer newSnapshot = mPm.snapshotComputer();
         if (!unsuspendedPackages.isEmpty()) {
             final String[] packageArray = unsuspendedPackages.toArray(
                     new String[unsuspendedPackages.size()]);
             mBroadcastHelper.sendMyPackageSuspendedOrUnsuspended(newSnapshot, packageArray,
-                    false, userId);
+                    false, targetUserId);
             mBroadcastHelper.sendPackagesSuspendedOrUnsuspendedForUser(newSnapshot,
                     Intent.ACTION_PACKAGES_UNSUSPENDED,
-                    packageArray, unsuspendedUids.toArray(), false, userId);
+                    packageArray, unsuspendedUids.toArray(), false, targetUserId);
         }
     }
 
@@ -404,7 +409,7 @@
      * @return The name of suspending package.
      */
     @Nullable
-    String getSuspendingPackage(@NonNull Computer snapshot, @NonNull String suspendedPackage,
+    UserPackage getSuspendingPackage(@NonNull Computer snapshot, @NonNull String suspendedPackage,
             int userId, int callingUid) {
         final PackageStateInternal packageState = snapshot.getPackageStateInternal(
                 suspendedPackage, callingUid);
@@ -417,13 +422,13 @@
             return null;
         }
 
-        String suspendingPackage = null;
-        String suspendedBySystem = null;
-        String qasPackage = null;
+        UserPackage suspendingPackage = null;
+        UserPackage suspendedBySystem = null;
+        UserPackage qasPackage = null;
         for (int i = 0; i < userState.getSuspendParams().size(); i++) {
             suspendingPackage = userState.getSuspendParams().keyAt(i);
             var suspendParams = userState.getSuspendParams().valueAt(i);
-            if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
+            if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage.packageName)) {
                 suspendedBySystem = suspendingPackage;
             }
             if (suspendParams.isQuarantined() && qasPackage == null) {
@@ -451,7 +456,7 @@
      */
     @Nullable
     SuspendDialogInfo getSuspendedDialogInfo(@NonNull Computer snapshot,
-            @NonNull String suspendedPackage, @NonNull String suspendingPackage, int userId,
+            @NonNull String suspendedPackage, @NonNull UserPackage suspendingPackage, int userId,
             int callingUid) {
         final PackageStateInternal packageState = snapshot.getPackageStateInternal(
                 suspendedPackage, callingUid);
@@ -464,7 +469,7 @@
             return null;
         }
 
-        final WatchedArrayMap<String, SuspendParams> suspendParamsMap =
+        final WatchedArrayMap<UserPackage, SuspendParams> suspendParamsMap =
                 userState.getSuspendParams();
         if (suspendParamsMap == null) {
             return null;
@@ -493,34 +498,36 @@
      * be suspended or not.
      *
      * @param packageNames  The package names to check suspendability for.
-     * @param userId The user to check in
+     * @param targetUserId The user to check in
      * @param callingUid The caller's uid.
      * @return An array containing results of the checks
      */
     @NonNull
     boolean[] canSuspendPackageForUser(@NonNull Computer snapshot, @NonNull String[] packageNames,
-            int userId, int callingUid) {
+            int targetUserId, int callingUid) {
         final boolean[] canSuspend = new boolean[packageNames.length];
-        final boolean isCallerOwner = isCallerDeviceOrProfileOwner(snapshot, userId, callingUid);
+        final boolean isCallerOwner =
+                isCallerDeviceOrProfileOwner(snapshot, targetUserId, callingUid);
         final long token = Binder.clearCallingIdentity();
         try {
             final DefaultAppProvider defaultAppProvider = mInjector.getDefaultAppProvider();
-            final String activeLauncherPackageName = defaultAppProvider.getDefaultHome(userId);
-            final String dialerPackageName = defaultAppProvider.getDefaultDialer(userId);
+            final String activeLauncherPackageName =
+                    defaultAppProvider.getDefaultHome(targetUserId);
+            final String dialerPackageName = defaultAppProvider.getDefaultDialer(targetUserId);
             final String requiredInstallerPackage =
-                    getKnownPackageName(snapshot, KnownPackages.PACKAGE_INSTALLER, userId);
+                    getKnownPackageName(snapshot, KnownPackages.PACKAGE_INSTALLER, targetUserId);
             final String requiredUninstallerPackage =
-                    getKnownPackageName(snapshot, KnownPackages.PACKAGE_UNINSTALLER, userId);
+                    getKnownPackageName(snapshot, KnownPackages.PACKAGE_UNINSTALLER, targetUserId);
             final String requiredVerifierPackage =
-                    getKnownPackageName(snapshot, KnownPackages.PACKAGE_VERIFIER, userId);
+                    getKnownPackageName(snapshot, KnownPackages.PACKAGE_VERIFIER, targetUserId);
             final String requiredPermissionControllerPackage =
                     getKnownPackageName(snapshot, KnownPackages.PACKAGE_PERMISSION_CONTROLLER,
-                            userId);
+                            targetUserId);
             for (int i = 0; i < packageNames.length; i++) {
                 canSuspend[i] = false;
                 final String packageName = packageNames[i];
 
-                if (mPm.isPackageDeviceAdmin(packageName, userId)) {
+                if (mPm.isPackageDeviceAdmin(packageName, targetUserId)) {
                     Slog.w(TAG, "Cannot suspend package \"" + packageName
                             + "\": has an active device admin");
                     continue;
@@ -555,12 +562,12 @@
                             + "\": required for permissions management");
                     continue;
                 }
-                if (mProtectedPackages.isPackageStateProtected(userId, packageName)) {
+                if (mProtectedPackages.isPackageStateProtected(targetUserId, packageName)) {
                     Slog.w(TAG, "Cannot suspend package \"" + packageName
                             + "\": protected package");
                     continue;
                 }
-                if (!isCallerOwner && snapshot.getBlockUninstall(userId, packageName)) {
+                if (!isCallerOwner && snapshot.getBlockUninstall(targetUserId, packageName)) {
                     Slog.w(TAG, "Cannot suspend package \"" + packageName
                             + "\": blocked by admin");
                     continue;
@@ -572,7 +579,7 @@
                 PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
                 AndroidPackage pkg = packageState == null ? null : packageState.getPkg();
                 if (pkg != null) {
-                    final int uid = UserHandle.getUid(userId, packageState.getAppId());
+                    final int uid = UserHandle.getUid(targetUserId, packageState.getAppId());
                     // Cannot suspend SDK libs as they are controlled by SDK manager.
                     if (pkg.isSdkLibrary()) {
                         Slog.w(TAG, "Cannot suspend package: " + packageName
@@ -614,20 +621,6 @@
                         == AppOpsManager.MODE_ALLOWED;
     }
 
-    /**
-     * Suspends packages on behalf of an admin.
-     *
-     * @return array of packages that are unsuspendable, either because admin is not allowed to
-     * suspend them (e.g. current dialer) or there was other problem (e.g. package not found).
-     */
-    public String[] setPackagesSuspendedByAdmin(
-            Computer snapshot, int userId, String[] packageNames, boolean suspend) {
-        return setPackagesSuspended(snapshot, packageNames, suspend,
-                null /* appExtras */, null /* launcherExtras */, null /* dialogInfo */,
-                PackageManagerService.PLATFORM_PACKAGE_NAME, userId, Process.SYSTEM_UID,
-                false /* quarantined */);
-    }
-
     private String getKnownPackageName(@NonNull Computer snapshot,
             @KnownPackages.KnownPackage int knownPackage, int userId) {
         final String[] knownPackages =
@@ -635,14 +628,15 @@
         return knownPackages.length > 0 ? knownPackages[0] : null;
     }
 
-    private boolean isCallerDeviceOrProfileOwner(@NonNull Computer snapshot, int userId,
+    private boolean isCallerDeviceOrProfileOwner(@NonNull Computer snapshot, int targetUserId,
             int callingUid) {
         if (callingUid == SYSTEM_UID) {
             return true;
         }
-        final String ownerPackage = mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId);
+        final String ownerPackage =
+                mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(targetUserId);
         if (ownerPackage != null) {
-            return callingUid == snapshot.getPackageUidInternal(ownerPackage, 0, userId,
+            return callingUid == snapshot.getPackageUidInternal(ownerPackage, 0, targetUserId,
                     callingUid);
         }
         return false;
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index fa54f0b..d0fe964 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -28,6 +28,7 @@
 import android.content.pm.FallbackCategoryProvider;
 import android.content.pm.FeatureGroupInfo;
 import android.content.pm.FeatureInfo;
+import android.content.pm.Flags;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageItemInfo;
@@ -474,7 +475,34 @@
         }
         info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
                 ? null : usesLibraryFiles.toArray(new String[0]);
-        info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;
+
+
+        if (!Flags.sdkLibIndependence()) {
+            info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;
+            info.optionalSharedLibraryInfos = null;
+        } else {
+            // sharedLibraryInfos contains all shared libraries that the app depends on (including
+            // the optional sdk libraries)
+            info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;
+            String[] libsNames = pkgSetting.getUsesSdkLibraries();
+            boolean[] libsOptional = pkgSetting.getUsesSdkLibrariesOptional();
+            List<SharedLibraryInfo> optionalSdkLibraries = null;
+            if (!ArrayUtils.isEmpty(libsOptional) && !ArrayUtils.isEmpty(libsNames)
+                    && libsNames.length == libsOptional.length) {
+                for (SharedLibraryInfo info1 : usesLibraryInfos) {
+                    if (info1.getType() == SharedLibraryInfo.TYPE_SDK_PACKAGE) {
+                        int index = ArrayUtils.indexOf(libsNames, info1.getName());
+                        if (index >= 0 && libsOptional[index]) {
+                            if (optionalSdkLibraries == null) {
+                                optionalSdkLibraries = new ArrayList<>();
+                            }
+                            optionalSdkLibraries.add(info1);
+                        }
+                    }
+                }
+            }
+            info.optionalSharedLibraryInfos = optionalSdkLibraries;
+        }
         if (info.category == ApplicationInfo.CATEGORY_UNDEFINED) {
             info.category = pkgSetting.getCategoryOverride();
         }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
index 2f4ad2d8..15b693c 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
+import android.content.pm.UserPackage;
 import android.content.pm.overlay.OverlayPaths;
 import android.util.ArraySet;
 import android.util.Pair;
@@ -173,7 +174,7 @@
 
     @Nullable
     @Override
-    public WatchedArrayMap<String, SuspendParams> getSuspendParams() {
+    public WatchedArrayMap<UserPackage, SuspendParams> getSuspendParams() {
         return null;
     }
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
index c5ef525..7a5a14d 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
+import android.content.pm.UserPackage;
 import android.content.pm.overlay.OverlayPaths;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -121,7 +122,7 @@
      * Suspending package to suspend params
      */
     @Nullable
-    private WatchedArrayMap<String, SuspendParams> mSuspendParams;
+    private WatchedArrayMap<UserPackage, SuspendParams> mSuspendParams;
 
     @Nullable
     private WatchedArrayMap<ComponentName, Pair<String, Integer>> mComponentLabelIconOverrideMap;
@@ -369,7 +370,10 @@
         return !CollectionUtils.isEmpty(mSuspendParams);
     }
 
-    public PackageUserStateImpl putSuspendParams(@NonNull String suspendingPackage,
+    /**
+     * Adds or updates suspension params by the given package.
+     */
+    public PackageUserStateImpl putSuspendParams(@NonNull UserPackage suspendingPackage,
             @Nullable SuspendParams suspendParams) {
         if (mSuspendParams == null) {
             mSuspendParams = new WatchedArrayMap<>();
@@ -384,7 +388,10 @@
         return this;
     }
 
-    public PackageUserStateImpl removeSuspension(@NonNull String suspendingPackage) {
+    /**
+     * Removes suspension by the given package.
+     */
+    public PackageUserStateImpl removeSuspension(@NonNull UserPackage suspendingPackage) {
         if (mSuspendParams != null) {
             mSuspendParams.remove(suspendingPackage);
             onChanged();
@@ -565,7 +572,7 @@
      * Suspending package to suspend params
      */
     public @NonNull PackageUserStateImpl setSuspendParams(
-            @NonNull ArrayMap<String, SuspendParams> value) {
+            @NonNull ArrayMap<UserPackage, SuspendParams> value) {
         if (value == null) {
             return this;
         }
@@ -778,7 +785,7 @@
      * Suspending package to suspend params
      */
     @DataClass.Generated.Member
-    public @Nullable WatchedArrayMap<String,SuspendParams> getSuspendParams() {
+    public @Nullable WatchedArrayMap<UserPackage,SuspendParams> getSuspendParams() {
         return mSuspendParams;
     }
 
@@ -830,7 +837,7 @@
      * Suspending package to suspend params
      */
     @DataClass.Generated.Member
-    public @NonNull PackageUserStateImpl setSuspendParams(@NonNull WatchedArrayMap<String,SuspendParams> value) {
+    public @NonNull PackageUserStateImpl setSuspendParams(@NonNull WatchedArrayMap<UserPackage,SuspendParams> value) {
         mSuspendParams = value;
         return this;
     }
@@ -909,10 +916,10 @@
     }
 
     @DataClass.Generated(
-            time = 1701470095849L,
+            time = 1701864813354L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java",
-            inputSignatures = "private  int mBooleans\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate  long mCeDataInode\nprivate  long mDeDataInode\nprivate  int mDistractionFlags\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate  void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic  boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic  void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic  com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic  com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate  boolean watchableEquals(com.android.server.utils.Watchable)\nprivate  int watchableHashCode()\nprivate  boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate  int snapshotHashCode()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isQuarantined()\npublic @java.lang.Override boolean dataExists()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\nprivate static final  int INSTALLED\nprivate static final  int STOPPED\nprivate static final  int NOT_LAUNCHED\nprivate static final  int HIDDEN\nprivate static final  int INSTANT_APP\nprivate static final  int VIRTUAL_PRELOADED\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
+            inputSignatures = "private  int mBooleans\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate  long mCeDataInode\nprivate  long mDeDataInode\nprivate  int mDistractionFlags\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.pm.UserPackage,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate  void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic  boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic  void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic  com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(android.content.pm.UserPackage,com.android.server.pm.pkg.SuspendParams)\npublic  com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(android.content.pm.UserPackage)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<android.content.pm.UserPackage,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate  boolean watchableEquals(com.android.server.utils.Watchable)\nprivate  int watchableHashCode()\nprivate  boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate  int snapshotHashCode()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isQuarantined()\npublic @java.lang.Override boolean dataExists()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\nprivate static final  int INSTALLED\nprivate static final  int STOPPED\nprivate static final  int NOT_LAUNCHED\nprivate static final  int HIDDEN\nprivate static final  int INSTANT_APP\nprivate static final  int VIRTUAL_PRELOADED\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
index 46cc830..f8d745c 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateInternal.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
+import android.content.pm.UserPackage;
 import android.content.pm.pkg.FrameworkPackageUserState;
 import android.util.Pair;
 
@@ -38,7 +39,7 @@
 
     // TODO: Make non-null with emptyMap()
     @Nullable
-    WatchedArrayMap<String, SuspendParams> getSuspendParams();
+    WatchedArrayMap<UserPackage, SuspendParams> getSuspendParams();
 
     @Nullable
     WatchedArraySet<String> getDisabledComponentsNoCopy();
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
index 8430cf7..253eb40 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java
@@ -21,6 +21,7 @@
 import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.UserPackage;
 import android.content.pm.overlay.OverlayPaths;
 import android.util.ArraySet;
 
@@ -349,7 +350,7 @@
 
             @NonNull
             @Override
-            public PackageUserStateWrite putSuspendParams(@NonNull String suspendingPackage,
+            public PackageUserStateWrite putSuspendParams(@NonNull UserPackage suspendingPackage,
                     @Nullable SuspendParams suspendParams) {
                 if (mUserState != null) {
                     mUserState.putSuspendParams(suspendingPackage, suspendParams);
@@ -359,7 +360,7 @@
 
             @NonNull
             @Override
-            public PackageUserStateWrite removeSuspension(@NonNull String suspendingPackage) {
+            public PackageUserStateWrite removeSuspension(@NonNull UserPackage suspendingPackage) {
                 if (mUserState != null) {
                     mUserState.removeSuspension(suspendingPackage);
                 }
diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
index 0c6c672..f6b2104 100644
--- a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
+++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
+import android.content.pm.UserPackage;
 import android.content.pm.overlay.OverlayPaths;
 
 import com.android.server.pm.pkg.PackageUserStateImpl;
@@ -38,11 +39,11 @@
             @PackageManager.DistractionRestriction int restrictionFlags);
 
     @NonNull
-    PackageUserStateWrite putSuspendParams(@NonNull String suspendingPackage,
+    PackageUserStateWrite putSuspendParams(@NonNull UserPackage suspendingPackage,
             @Nullable SuspendParams suspendParams);
 
     @NonNull
-    PackageUserStateWrite removeSuspension(@NonNull String suspendingPackage);
+    PackageUserStateWrite removeSuspension(@NonNull UserPackage suspendingPackage);
 
     @NonNull
     PackageUserStateWrite setHidden(boolean hidden);
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index f9d344b..1b45c1b 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -48,6 +48,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -335,19 +336,19 @@
             return false;
         }
         final String suspendedPackage = mAInfo.applicationInfo.packageName;
-        final String suspendingPackage = pmi.getSuspendingPackage(suspendedPackage, mUserId);
-        if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
+        final UserPackage suspender = pmi.getSuspendingPackage(suspendedPackage, mUserId);
+        if (suspender != null && PLATFORM_PACKAGE_NAME.equals(suspender.packageName)) {
             return interceptSuspendedByAdminPackage();
         }
         final SuspendDialogInfo dialogInfo = pmi.getSuspendedDialogInfo(suspendedPackage,
-                suspendingPackage, mUserId);
+                suspender, mUserId);
         final Bundle crossProfileOptions = hasCrossProfileAnimation()
                 ? ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle()
                 : null;
         final IntentSender target = createIntentSenderForOriginalIntent(mCallingUid,
                 FLAG_IMMUTABLE);
         mIntent = SuspendedAppActivity.createSuspendedAppInterceptIntent(suspendedPackage,
-                suspendingPackage, dialogInfo, crossProfileOptions, target, mUserId);
+                suspender, dialogInfo, crossProfileOptions, target, mUserId);
         mCallingPid = mRealCallingPid;
         mCallingUid = mRealCallingUid;
         mResolvedType = null;
diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
index 0d15fc9..2b841fd 100644
--- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
+++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH;
 
 import static com.android.internal.R.bool.config_unfoldTransitionEnabled;
 import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
@@ -110,15 +111,6 @@
             return;
         }
 
-        final TransitionRequestInfo.DisplayChange displayChange =
-                new TransitionRequestInfo.DisplayChange(displayId);
-
-        final Rect startAbsBounds = new Rect(0, 0, oldDisplayWidth, oldDisplayHeight);
-        displayChange.setStartAbsBounds(startAbsBounds);
-        final Rect endAbsBounds = new Rect(0, 0, newDisplayWidth, newDisplayHeight);
-        displayChange.setEndAbsBounds(endAbsBounds);
-        displayChange.setPhysicalDisplayChanged(true);
-
         mTransition = null;
 
         if (mTransitionController.isCollecting()) {
@@ -128,10 +120,20 @@
 
             // Make sure that transition is not ready until we finish the remote display change
             mTransition.setReady(mDisplayContent, false);
+            mTransition.addFlag(TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH);
 
             ProtoLog.d(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                     "Adding display switch to existing collecting transition");
         } else {
+            final TransitionRequestInfo.DisplayChange displayChange =
+                    new TransitionRequestInfo.DisplayChange(displayId);
+
+            final Rect startAbsBounds = new Rect(0, 0, oldDisplayWidth, oldDisplayHeight);
+            displayChange.setStartAbsBounds(startAbsBounds);
+            final Rect endAbsBounds = new Rect(0, 0, newDisplayWidth, newDisplayHeight);
+            displayChange.setEndAbsBounds(endAbsBounds);
+            displayChange.setPhysicalDisplayChanged(true);
+
             mTransition = mTransitionController.requestTransitionIfNeeded(TRANSIT_CHANGE,
                     0 /* flags */,
                     mDisplayContent, mDisplayContent, null /* remoteTransition */,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e0a2f30..a490013 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2469,7 +2469,7 @@
     private void migratePersonalAppSuspensionLocked(
             int doUserId, int poUserId, ActiveAdmin poAdmin) {
         final PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
-        if (!pmi.isSuspendingAnyPackages(PLATFORM_PACKAGE_NAME, doUserId)) {
+        if (!pmi.isAdminSuspendingAnyPackages(doUserId)) {
             Slogf.i(LOG_TAG, "DO is not suspending any apps.");
             return;
         }
@@ -2480,7 +2480,7 @@
             poAdmin.mSuspendPersonalApps = true;
         } else {
             Slogf.i(LOG_TAG, "PO isn't targeting R+, unsuspending personal apps.");
-            pmi.unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, doUserId);
+            pmi.unsuspendAdminSuspendedPackages(doUserId);
         }
     }
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 6570ce1..506dbe8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -16,8 +16,6 @@
 
 package com.android.server.devicepolicy;
 
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppGlobals;
@@ -287,7 +285,7 @@
                 suspendPersonalAppsInPackageManager(context, userId);
             } else {
                 LocalServices.getService(PackageManagerInternal.class)
-                        .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, userId);
+                        .unsuspendAdminSuspendedPackages(userId);
             }
         });
         return true;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index b396cf4..40d3d5c 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -45,15 +45,19 @@
 import android.app.PropertyInvalidatedCache;
 import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.Flags;
 import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.os.BaseBundle;
 import android.os.Message;
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -67,6 +71,7 @@
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.permission.persistence.RuntimePermissionsPersistence;
 import com.android.server.LocalServices;
+import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.permission.LegacyPermissionDataProvider;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.ArchiveState;
@@ -85,6 +90,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -109,6 +115,9 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class PackageManagerSettingsTests {
+
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
     private static final String PACKAGE_NAME_1 = "com.android.app1";
     private static final String PACKAGE_NAME_2 = "com.android.app2";
     private static final String PACKAGE_NAME_3 = "com.android.app3";
@@ -140,6 +149,7 @@
     public void setup() {
         // Disable binder caches in this process.
         PropertyInvalidatedCache.disableForTestMode();
+
     }
 
     @Before
@@ -161,6 +171,107 @@
         deleteFolder(InstrumentationRegistry.getContext().getFilesDir());
     }
 
+    @Test
+    public void testApplicationInfoForUseSdkOptionalEnabled() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_SDK_LIB_INDEPENDENCE);
+
+        // Create basic information for SDK lib
+        final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
+        ps1.setPkg(((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed())
+                .setUid(ps1.getAppId())
+                .setSystem(true)
+                .hideAsFinal());
+        ps1.setUsesSdkLibraries(new String[] { "com.example.sdk.one" });
+        ps1.setUsesSdkLibrariesVersionsMajor(new long[] { 12 });
+        ps1.setUsesSdkLibrariesOptional(new boolean[] {true});
+        ps1.addUsesLibraryInfo(new SharedLibraryInfo("path1",
+                "packageName1",
+                Collections.emptyList(),
+                "com.example.sdk.one",
+                12 /*version*/,
+                SharedLibraryInfo.TYPE_SDK_PACKAGE,
+                null /*declaringPackage*/,
+                null /*dependentPackages*/,
+                null /*dependencies*/,
+                false /*isNative*/));
+        ps1.addUsesLibraryInfo(new SharedLibraryInfo("path11",
+                "packageName11",
+                Collections.emptyList(),
+                "com.example.sdk.oneone",
+                1212 /*version*/,
+                SharedLibraryInfo.TYPE_STATIC,
+                null /*declaringPackage*/,
+                null /*dependentPackages*/,
+                null /*dependencies*/,
+                false /*isNative*/));
+        ApplicationInfo appInfo1 = PackageInfoUtils.generateApplicationInfo(ps1.getAndroidPackage(),
+                0 /*flags*/, ps1.getUserStateOrDefault(0), 0 /*userId*/,
+                ps1);
+        assertThat(appInfo1, notNullValue());
+        assertThat(appInfo1.sharedLibraryInfos, notNullValue());
+        assertThat(appInfo1.optionalSharedLibraryInfos, notNullValue());
+        assertEquals(appInfo1.sharedLibraryInfos.get(0).getName(), "com.example.sdk.one");
+        assertEquals(appInfo1.optionalSharedLibraryInfos.get(0).getName(), "com.example.sdk.one");
+
+        final PackageSetting ps2 = createPackageSetting(PACKAGE_NAME_2);
+        ps2.setPkg(((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_2).hideAsParsed())
+                .setUid(ps2.getAppId())
+                .setSystem(true)
+                .hideAsFinal());
+        ps2.setUsesSdkLibraries(new String[] { "com.example.sdk.two" });
+        ps2.setUsesSdkLibrariesVersionsMajor(new long[] { 34 });
+        ps2.setUsesSdkLibrariesOptional(new boolean[] {false});
+        ps2.addUsesLibraryInfo(new SharedLibraryInfo("path2",
+                "packageName2",
+                Collections.emptyList(),
+                "com.example.sdk.two",
+                34 /*version*/,
+                SharedLibraryInfo.TYPE_SDK_PACKAGE,
+                null /*declaringPackage*/,
+                null /*dependentPackages*/,
+                null /*dependencies*/,
+                false /*isNative*/));
+        ApplicationInfo appInfo2 = PackageInfoUtils.generateApplicationInfo(ps2.getAndroidPackage(),
+                0 /*flags*/, ps2.getUserStateOrDefault(0), 0 /*userId*/,
+                ps2);
+        assertThat(appInfo2, notNullValue());
+        assertThat(appInfo2.sharedLibraryInfos, notNullValue());
+        assertThat(appInfo2.optionalSharedLibraryInfos, nullValue());
+        assertEquals(appInfo2.sharedLibraryInfos.get(0).getName(), "com.example.sdk.two");
+    }
+
+    @Test
+    public void testApplicationInfoForUseSdkOptionalDisabled() throws Exception {
+        mSetFlagsRule.disableFlags(Flags.FLAG_SDK_LIB_INDEPENDENCE);
+
+        // Create basic information for SDK lib
+        final PackageSetting ps1 = createPackageSetting(PACKAGE_NAME_1);
+        ps1.setPkg(((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed())
+                .setUid(ps1.getAppId())
+                .setSystem(true)
+                .hideAsFinal());
+        ps1.setUsesSdkLibraries(new String[] { "com.example.sdk.one" });
+        ps1.setUsesSdkLibrariesVersionsMajor(new long[] { 12 });
+        ps1.setUsesSdkLibrariesOptional(new boolean[] {true});
+        ps1.addUsesLibraryInfo(new SharedLibraryInfo("path1",
+                "packageName1",
+                Collections.emptyList(),
+                "com.example.sdk.one",
+                12 /*version*/,
+                SharedLibraryInfo.TYPE_SDK_PACKAGE,
+                null /*declaringPackage*/,
+                null /*dependentPackages*/,
+                null /*dependencies*/,
+                false /*isNative*/));
+        ApplicationInfo appInfo1 = PackageInfoUtils.generateApplicationInfo(ps1.getAndroidPackage(),
+                0 /*flags*/, ps1.getUserStateOrDefault(0), 0 /*userId*/,
+                ps1);
+        assertThat(appInfo1, notNullValue());
+        assertThat(appInfo1.sharedLibraryInfos, notNullValue());
+        assertThat(appInfo1.optionalSharedLibraryInfos, nullValue());
+        assertEquals(appInfo1.sharedLibraryInfos.get(0).getName(), "com.example.sdk.one");
+    }
+
     /** make sure our initialized KeySetManagerService metadata matches packages.xml */
     @Test
     public void testReadKeySetSettings() throws Exception {
@@ -315,7 +426,7 @@
         PackageUserStateInternal packageUserState1 = ps1.readUserState(0);
         assertThat(packageUserState1.isSuspended(), is(true));
         assertThat(packageUserState1.getSuspendParams().size(), is(1));
-        assertThat(packageUserState1.getSuspendParams().keyAt(0), is("android"));
+        assertThat(packageUserState1.getSuspendParams().keyAt(0), is(UserPackage.of(0, "android")));
         assertThat(packageUserState1.getSuspendParams().valueAt(0).getAppExtras(), is(nullValue()));
         assertThat(packageUserState1.getSuspendParams().valueAt(0).getDialogInfo(),
                 is(nullValue()));
@@ -327,7 +438,7 @@
         packageUserState1 = ps1.readUserState(0);
         assertThat(packageUserState1.isSuspended(), is(true));
         assertThat(packageUserState1.getSuspendParams().size(), is(1));
-        assertThat(packageUserState1.getSuspendParams().keyAt(0), is("android"));
+        assertThat(packageUserState1.getSuspendParams().keyAt(0), is(UserPackage.of(0, "android")));
         assertThat(packageUserState1.getSuspendParams().valueAt(0).getAppExtras(), is(nullValue()));
         assertThat(packageUserState1.getSuspendParams().valueAt(0).getDialogInfo(),
                 is(nullValue()));
@@ -362,7 +473,8 @@
         watcher.verifyNoChangeReported("readUserState");
         assertThat(packageUserState1.isSuspended(), is(true));
         assertThat(packageUserState1.getSuspendParams().size(), is(1));
-        assertThat(packageUserState1.getSuspendParams().keyAt(0), is(PACKAGE_NAME_3));
+        assertThat(packageUserState1.getSuspendParams().keyAt(0),
+                is(UserPackage.of(0, PACKAGE_NAME_3)));
         final SuspendParams params = packageUserState1.getSuspendParams().valueAt(0);
         watcher.verifyNoChangeReported("fetch user state");
         assertThat(params, is(notNullValue()));
@@ -413,19 +525,24 @@
                 .setNeutralButtonAction(BUTTON_ACTION_UNSUSPEND)
                 .build();
 
-        ps1.modifyUserState(0).putSuspendParams("suspendingPackage1",
+        UserPackage suspender1 = UserPackage.of(0, "suspendingPackage1");
+        UserPackage suspender2 = UserPackage.of(0, "suspendingPackage2");
+        UserPackage suspender3 = UserPackage.of(0, "suspendingPackage3");
+        UserPackage irrelevantSuspender = UserPackage.of(0, "irrelevant");
+
+        ps1.modifyUserState(0).putSuspendParams(suspender1,
                 new SuspendParams(dialogInfo1, appExtras1, launcherExtras1));
-        ps1.modifyUserState(0).putSuspendParams("suspendingPackage2",
+        ps1.modifyUserState(0).putSuspendParams(suspender2,
                 new SuspendParams(dialogInfo2, appExtras2, launcherExtras2));
         settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1);
         watcher.verifyChangeReported("put package 1");
 
-        ps2.modifyUserState(0).putSuspendParams("suspendingPackage3",
+        ps2.modifyUserState(0).putSuspendParams(suspender3,
                 new SuspendParams(null, appExtras1, null));
         settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2);
         watcher.verifyChangeReported("put package 2");
 
-        ps3.modifyUserState(0).removeSuspension("irrelevant");
+        ps3.modifyUserState(0).removeSuspension(irrelevantSuspender);
         settingsUnderTest.mPackages.put(PACKAGE_NAME_3, ps3);
         watcher.verifyChangeReported("put package 3");
 
@@ -450,7 +567,7 @@
         assertThat(readPus1.getSuspendParams().size(), is(2));
         watcher.verifyNoChangeReported("read package param");
 
-        assertThat(readPus1.getSuspendParams().keyAt(0), is("suspendingPackage1"));
+        assertThat(readPus1.getSuspendParams().keyAt(0), is(suspender1));
         final SuspendParams params11 = readPus1.getSuspendParams().valueAt(0);
         watcher.verifyNoChangeReported("read package param");
         assertThat(params11, is(notNullValue()));
@@ -460,7 +577,7 @@
                 is(true));
         watcher.verifyNoChangeReported("read package param");
 
-        assertThat(readPus1.getSuspendParams().keyAt(1), is("suspendingPackage2"));
+        assertThat(readPus1.getSuspendParams().keyAt(1), is(suspender2));
         final SuspendParams params12 = readPus1.getSuspendParams().valueAt(1);
         assertThat(params12, is(notNullValue()));
         assertThat(params12.getDialogInfo(), is(dialogInfo2));
@@ -473,7 +590,7 @@
                 .readUserState(0);
         assertThat(readPus2.isSuspended(), is(true));
         assertThat(readPus2.getSuspendParams().size(), is(1));
-        assertThat(readPus2.getSuspendParams().keyAt(0), is("suspendingPackage3"));
+        assertThat(readPus2.getSuspendParams().keyAt(0), is(suspender3));
         final SuspendParams params21 = readPus2.getSuspendParams().valueAt(0);
         assertThat(params21, is(notNullValue()));
         assertThat(params21.getDialogInfo(), is(nullValue()));
@@ -1024,7 +1141,8 @@
                 .setNeutralButtonText(0x11220003)
                 .setNeutralButtonAction(BUTTON_ACTION_MORE_DETAILS)
                 .build();
-        origPkgSetting01.modifyUserState(0).putSuspendParams("suspendingPackage1",
+        origPkgSetting01.modifyUserState(0).putSuspendParams(
+                UserPackage.of(0, "suspendingPackage1"),
                 new SuspendParams(dialogInfo1, appExtras1, launcherExtras1));
         origPkgSetting01.setPkg(mockAndroidPackage(origPkgSetting01));
         final PackageSetting testPkgSetting01 = new PackageSetting(
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
index c0c7032..9780440 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
@@ -27,6 +27,7 @@
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.SuspendDialogInfo;
+import android.content.pm.UserPackage;
 import android.content.pm.overlay.OverlayPaths;
 import android.os.PersistableBundle;
 import android.platform.test.annotations.Presubmit;
@@ -89,7 +90,7 @@
         assertThat(testUserState.equals(oldUserState), is(false));
 
         oldUserState = new PackageUserStateImpl();
-        oldUserState.putSuspendParams("suspendingPackage",
+        oldUserState.putSuspendParams(UserPackage.of(0, "suspendingPackage"),
                 new SuspendParams(null, new PersistableBundle(), null));
         assertThat(testUserState.equals(oldUserState), is(false));
 
@@ -220,6 +221,8 @@
         final PersistableBundle launcherExtras2 = createPersistableBundle(null, 0, "name",
                 "launcherExtras2", null, 0);
 
+        final int suspendingUser1 = 0;
+        final int suspendingUser2 = 10;
         final String suspendingPackage1 = "package1";
         final String suspendingPackage2 = "package2";
 
@@ -230,12 +233,12 @@
                 .setMessage("dialogMessage2")
                 .build();
 
-        final ArrayMap<String, SuspendParams> paramsMap1 = new ArrayMap<>();
-        paramsMap1.put(suspendingPackage1, createSuspendParams(dialogInfo1, appExtras1,
-                launcherExtras1));
-        final ArrayMap<String, SuspendParams> paramsMap2 = new ArrayMap<>();
-        paramsMap2.put(suspendingPackage2, createSuspendParams(dialogInfo2,
-                appExtras2, launcherExtras2));
+        final ArrayMap<UserPackage, SuspendParams> paramsMap1 = new ArrayMap<>();
+        paramsMap1.put(UserPackage.of(suspendingUser1, suspendingPackage1),
+                createSuspendParams(dialogInfo1, appExtras1, launcherExtras1));
+        final ArrayMap<UserPackage, SuspendParams> paramsMap2 = new ArrayMap<>();
+        paramsMap2.put(UserPackage.of(suspendingUser2, suspendingPackage2),
+                createSuspendParams(dialogInfo2, appExtras2, launcherExtras2));
 
 
         final PackageUserStateImpl testUserState1 = new PackageUserStateImpl();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index ae53e70..7444403 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -19,6 +19,7 @@
 import android.app.AppOpsManager
 import android.content.Intent
 import android.content.pm.SuspendDialogInfo
+import android.content.pm.UserPackage
 import android.os.Binder
 import android.os.PersistableBundle
 import com.android.server.testutils.any
@@ -41,12 +42,18 @@
                 .thenReturn(AppOpsManager.MODE_DEFAULT)
     }
 
+    companion object {
+        val doUserPackage = UserPackage.of(TEST_USER_ID, DEVICE_OWNER_PACKAGE)
+        val platformUserPackage = UserPackage.of(TEST_USER_ID, PLATFORM_PACKAGE_NAME)
+        val testUserPackage1 = UserPackage.of(TEST_USER_ID, TEST_PACKAGE_1)
+    }
+
     @Test
     fun setPackagesSuspended() {
         val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
         val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 targetPackages, true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
         testHandler.flush()
 
@@ -63,14 +70,14 @@
     fun setPackagesSuspended_emptyPackageName() {
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 null /* packageNames */, true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
 
         assertThat(failedNames).isNull()
 
         failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOfNulls(0), true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
 
         assertThat(failedNames).isEmpty()
@@ -80,7 +87,8 @@
     fun setPackagesSuspended_callerIsNotAllowed() {
         val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID,
+                null /* launcherExtras */, null /* dialogInfo */,
+                testUserPackage1, TEST_USER_ID,
                 Binder.getCallingUid(), false /* quarantined */)
 
         assertThat(failedNames).asList().hasSize(1)
@@ -91,7 +99,7 @@
     fun setPackagesSuspended_callerSuspendItself() {
         val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOf(DEVICE_OWNER_PACKAGE), true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
 
         assertThat(failedNames).asList().hasSize(1)
@@ -102,7 +110,7 @@
     fun setPackagesSuspended_nonexistentPackage() {
         val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOf(NONEXISTENT_PACKAGE), true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
 
         assertThat(failedNames).asList().hasSize(1)
@@ -115,7 +123,7 @@
             INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE, PERMISSION_CONTROLLER_PACKAGE)
         val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 knownPackages, true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)!!
 
         assertThat(failedNames.size).isEqualTo(knownPackages.size)
@@ -129,14 +137,14 @@
         val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 targetPackages, true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
         testHandler.flush()
         Mockito.clearInvocations(broadcastHelper)
         assertThat(failedNames).isEmpty()
         failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 targetPackages, false /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
         testHandler.flush()
 
@@ -184,7 +192,7 @@
         appExtras.putString(TEST_PACKAGE_1, TEST_PACKAGE_1)
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOf(TEST_PACKAGE_1), true /* suspended */, appExtras, null /* launcherExtras */,
-                null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
+                null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid,
                 false /* quarantined */)
         testHandler.flush()
         assertThat(failedNames).isEmpty()
@@ -202,22 +210,22 @@
         val targetPackages = arrayOf(TEST_PACKAGE_1, TEST_PACKAGE_2)
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 targetPackages, true /* suspended */, appExtras, null /* launcherExtras */,
-                null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
+                null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid,
                 false /* quarantined */)
         testHandler.flush()
         Mockito.clearInvocations(broadcastHelper)
         assertThat(failedNames).isEmpty()
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
-            TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+            TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage)
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
-            TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+            TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage)
         assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
             TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid)).isNotNull()
         assertThat(SuspendPackageHelper.getSuspendedPackageAppExtras(pms.snapshotComputer(),
             TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNotNull()
 
         suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
-            targetPackages, { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE },
+            targetPackages, { suspender -> suspender.packageName == DEVICE_OWNER_PACKAGE },
             TEST_USER_ID)
 
         testHandler.flush()
@@ -243,7 +251,7 @@
         launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2)
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */, launcherExtras,
-                null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
+                null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid,
                 false /* quarantined */)
         testHandler.flush()
         assertThat(failedNames).isEmpty()
@@ -258,7 +266,7 @@
     fun isPackageSuspended() {
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOf(TEST_PACKAGE_1), true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
+                null /* launcherExtras */, null /* dialogInfo */, doUserPackage,
                 TEST_USER_ID, deviceOwnerUid, false /* quarantined */)
         testHandler.flush()
         assertThat(failedNames).isEmpty()
@@ -273,13 +281,13 @@
         launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2)
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOf(TEST_PACKAGE_2), true /* suspended */, null /* appExtras */, launcherExtras,
-                null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
+                null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid,
                 false /* quarantined */)
         testHandler.flush()
         assertThat(failedNames).isEmpty()
 
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
-            TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+            TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage)
     }
 
     @Test
@@ -290,57 +298,57 @@
         // Suspend.
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 targetPackages, true /* suspended */, null /* appExtras */, launcherExtras,
-                null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
+                null /* dialogInfo */, doUserPackage, TEST_USER_ID, deviceOwnerUid,
                 false /* quarantined */)
         assertThat(failedNames).isEmpty()
         testHandler.flush()
 
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
-                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage)
 
         // Suspend by system.
         failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 targetPackages, true /* suspended */, null /* appExtras */, launcherExtras,
-                null /* dialogInfo */, PLATFORM_PACKAGE_NAME, TEST_USER_ID, deviceOwnerUid,
+                null /* dialogInfo */, platformUserPackage, TEST_USER_ID, deviceOwnerUid,
                 false /* quarantined */)
         assertThat(failedNames).isEmpty()
         testHandler.flush()
 
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
-                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(PLATFORM_PACKAGE_NAME)
+                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(platformUserPackage)
 
         // QAS by package1.
         failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 targetPackages, true /* suspended */, null /* appExtras */, launcherExtras,
-                null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid,
+                null /* dialogInfo */, testUserPackage1, TEST_USER_ID, deviceOwnerUid,
                 true /* quarantined */)
         assertThat(failedNames).isEmpty()
         testHandler.flush()
 
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
-                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(TEST_PACKAGE_1)
+                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(testUserPackage1)
 
         // Un-QAS by package1.
         suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
-                targetPackages, { suspendingPackage -> suspendingPackage == TEST_PACKAGE_1 },
+                targetPackages, { suspendingPackage -> suspendingPackage == testUserPackage1 },
                 TEST_USER_ID)
         testHandler.flush()
 
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
-                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(PLATFORM_PACKAGE_NAME)
+                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(platformUserPackage)
 
         // Un-suspend by system.
         suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
-                targetPackages, { suspendingPackage -> suspendingPackage == PLATFORM_PACKAGE_NAME },
+                targetPackages, { suspender -> suspender.packageName == PLATFORM_PACKAGE_NAME },
                 TEST_USER_ID)
         testHandler.flush()
 
         assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
-                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(doUserPackage)
 
         // Unsuspend.
         suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
-                targetPackages, { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE },
+                targetPackages, { suspendingPackage -> suspendingPackage == doUserPackage },
                 TEST_USER_ID)
         testHandler.flush()
 
@@ -354,13 +362,13 @@
             .setTitle(TEST_PACKAGE_1).build()
         var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
                 arrayOf(TEST_PACKAGE_1), true /* suspended */, null /* appExtras */,
-                null /* launcherExtras */, dialogInfo, DEVICE_OWNER_PACKAGE, TEST_USER_ID,
+                null /* launcherExtras */, dialogInfo, doUserPackage, TEST_USER_ID,
                 deviceOwnerUid, false /* quarantined */)
         testHandler.flush()
         assertThat(failedNames).isEmpty()
 
         val result = suspendPackageHelper.getSuspendedDialogInfo(pms.snapshotComputer(),
-            TEST_PACKAGE_1, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid)!!
+            TEST_PACKAGE_1, doUserPackage, TEST_USER_ID, deviceOwnerUid)!!
 
         assertThat(result.title).isEqualTo(TEST_PACKAGE_1)
     }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index 943a9c47..1dd64ff 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -18,7 +18,6 @@
 import static android.os.UserHandle.USER_SYSTEM;
 
 import static com.android.server.devicepolicy.DpmTestUtils.writeInputStreamToFile;
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -222,21 +221,21 @@
         prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.R);
 
         // Pretend some packages are suspended.
-        when(getServices().packageManagerInternal.isSuspendingAnyPackages(
-                PLATFORM_PACKAGE_NAME, USER_SYSTEM)).thenReturn(true);
+        when(getServices().packageManagerInternal.isAdminSuspendingAnyPackages(
+                USER_SYSTEM)).thenReturn(true);
 
         final DevicePolicyManagerServiceTestable dpms = bootDpmsUp();
 
         verify(getServices().packageManagerInternal, never())
-                .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM);
+                .unsuspendAdminSuspendedPackages(USER_SYSTEM);
 
         sendBroadcastWithUser(dpms, Intent.ACTION_USER_STARTED, USER_SYSTEM);
 
         // Verify that actual package suspension state is not modified after user start
         verify(getServices().packageManagerInternal, never())
-                .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM);
+                .unsuspendAdminSuspendedPackages(USER_SYSTEM);
         verify(getServices().ipackageManager, never()).setPackagesSuspendedAsUser(
-                any(), anyBoolean(), any(), any(), any(), anyInt(), any(), anyInt());
+                any(), anyBoolean(), any(), any(), any(), anyInt(), any(), anyInt(), anyInt());
 
         final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext);
         poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID);
@@ -255,14 +254,14 @@
         prepareAdmin1AsPo(COPE_PROFILE_USER_ID, Build.VERSION_CODES.Q);
 
         // Pretend some packages are suspended.
-        when(getServices().packageManagerInternal.isSuspendingAnyPackages(
-                PLATFORM_PACKAGE_NAME, USER_SYSTEM)).thenReturn(true);
+        when(getServices().packageManagerInternal.isAdminSuspendingAnyPackages(
+                USER_SYSTEM)).thenReturn(true);
 
         final DevicePolicyManagerServiceTestable dpms = bootDpmsUp();
 
         // Verify that apps get unsuspended.
         verify(getServices().packageManagerInternal)
-                .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, USER_SYSTEM);
+                .unsuspendAdminSuspendedPackages(USER_SYSTEM);
 
         final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext);
         poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index f4dac2c..2470403 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -63,7 +63,6 @@
 import static com.android.server.devicepolicy.DevicePolicyManagerService.ACTION_PROFILE_OFF_DEADLINE;
 import static com.android.server.devicepolicy.DevicePolicyManagerService.ACTION_TURN_PROFILE_ON_NOTIFICATION;
 import static com.android.server.devicepolicy.DpmMockContext.CALLER_USER_HANDLE;
-import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 import static com.android.server.testutils.TestUtils.assertExpectException;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -5080,7 +5079,7 @@
         verify(getServices().iwindowManager).refreshScreenCaptureDisabled();
         // Unsuspend personal apps
         verify(getServices().packageManagerInternal)
-                .unsuspendForSuspendingPackage(PLATFORM_PACKAGE_NAME, UserHandle.USER_SYSTEM);
+                .unsuspendAdminSuspendedPackages(UserHandle.USER_SYSTEM);
         verify(getServices().subscriptionManager).setSubscriptionUserHandle(0, null);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
                 FLAG_ENABLE_WORK_PROFILE_TELEPHONY, "false", false);
@@ -7535,7 +7534,7 @@
                 .cancel(eq(SystemMessageProto.SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED));
         // Verify that the apps are NOT unsuspeded.
         verify(getServices().ipackageManager, never()).setPackagesSuspendedAsUser(
-                any(), eq(false), any(), any(), any(), anyInt(), any(), anyInt());
+                any(), eq(false), any(), any(), any(), anyInt(), any(), anyInt(), anyInt());
         // Verify that DPC is invoked to check policy compliance.
         verify(mContext.spiedContext).startActivityAsUser(
                 MockUtils.checkIntentAction(ACTION_CHECK_POLICY_COMPLIANCE),
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index fb78574..0d25faf 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -60,6 +60,8 @@
 
 import com.android.internal.inputmethod.StartInputFlags;
 
+import com.google.common.truth.Truth;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -1336,6 +1338,54 @@
         }
     }
 
+    private static void verifySplitEnabledImeStr(@NonNull String enabledImeStr,
+            @NonNull String... expected) {
+        final ArrayList<String> actual = new ArrayList<>();
+        InputMethodUtils.splitEnabledImeStr(enabledImeStr, actual::add);
+        if (expected.length == 0) {
+            Truth.assertThat(actual).isEmpty();
+        } else {
+            Truth.assertThat(actual).containsExactlyElementsIn(expected);
+        }
+    }
+
+    @Test
+    public void testSplitEnabledImeStr() {
+        verifySplitEnabledImeStr("");
+        verifySplitEnabledImeStr("com.android/.ime1", "com.android/.ime1");
+        verifySplitEnabledImeStr("com.android/.ime1;1;2;3", "com.android/.ime1");
+        verifySplitEnabledImeStr("com.android/.ime1;1;2;3:com.android/.ime2",
+                "com.android/.ime1", "com.android/.ime2");
+        verifySplitEnabledImeStr("com.android/.ime1:com.android/.ime2",
+                "com.android/.ime1", "com.android/.ime2");
+        verifySplitEnabledImeStr("com.android/.ime1:com.android/.ime2:com.android/.ime3",
+                "com.android/.ime1", "com.android/.ime2", "com.android/.ime3");
+        verifySplitEnabledImeStr("com.android/.ime1;1:com.android/.ime2;1:com.android/.ime3;1",
+                "com.android/.ime1", "com.android/.ime2", "com.android/.ime3");
+    }
+
+    @Test
+    public void testConcatEnabledImeIds() {
+        Truth.assertThat(InputMethodUtils.concatEnabledImeIds("")).isEmpty();
+        Truth.assertThat(InputMethodUtils.concatEnabledImeIds("", "com.android/.ime1"))
+                .isEqualTo("com.android/.ime1");
+        Truth.assertThat(InputMethodUtils.concatEnabledImeIds(
+                        "com.android/.ime1", "com.android/.ime1"))
+                .isEqualTo("com.android/.ime1");
+        Truth.assertThat(InputMethodUtils.concatEnabledImeIds(
+                        "com.android/.ime1", "com.android/.ime2"))
+                .isEqualTo("com.android/.ime1:com.android/.ime2");
+        Truth.assertThat(InputMethodUtils.concatEnabledImeIds(
+                        "com.android/.ime1", "com.android/.ime2", "com.android/.ime3"))
+                .isEqualTo("com.android/.ime1:com.android/.ime2:com.android/.ime3");
+        Truth.assertThat(InputMethodUtils.concatEnabledImeIds(
+                        "com.android/.ime1:com.android/.ime2", "com.android/.ime1"))
+                .isEqualTo("com.android/.ime1:com.android/.ime2");
+        Truth.assertThat(InputMethodUtils.concatEnabledImeIds(
+                        "com.android/.ime1:com.android/.ime2", "com.android/.ime3"))
+                .isEqualTo("com.android/.ime1:com.android/.ime2:com.android/.ime3");
+    }
+
     @Test
     public void updateEnabledImeStringTest() {
         // No change cases
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 14b551a..776189e 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -219,6 +219,7 @@
 import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.ConversationChannelWrapper;
+import android.service.notification.DeviceEffectsApplier;
 import android.service.notification.NotificationListenerFilter;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationRankingUpdate;
@@ -635,6 +636,10 @@
     }
 
     private void initNMS() throws Exception {
+        initNMS(SystemService.PHASE_BOOT_COMPLETED);
+    }
+
+    private void initNMS(int upToBootPhase) throws Exception {
         mService = new TestableNotificationManagerService(mContext, mNotificationRecordLogger,
                 mNotificationInstanceIdSequence);
 
@@ -662,13 +667,21 @@
                 mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
                 mTelecomManager, mLogger, mTestFlagResolver, mPermissionManager,
                 mPowerManager, mPostNotificationTrackerFactory);
+
         // Return first true for RoleObserver main-thread check
         when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
-        mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
+
+        if (upToBootPhase >= SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
+        }
+
         Mockito.reset(mHistoryManager);
         verify(mHistoryManager, never()).onBootPhaseAppsCanStart();
-        mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START, mMainLooper);
-        verify(mHistoryManager).onBootPhaseAppsCanStart();
+
+        if (upToBootPhase >= SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
+            mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START, mMainLooper);
+            verify(mHistoryManager).onBootPhaseAppsCanStart();
+        }
 
         // TODO b/291907312: remove feature flag
         if (Flags.refactorAttentionHelper()) {
@@ -914,7 +927,9 @@
         mTestNotificationChannel.setAllowBubbles(channelEnabled);
     }
 
-    private void setUpPrefsForHistory(int uid, boolean globalEnabled) {
+    private void setUpPrefsForHistory(int uid, boolean globalEnabled) throws Exception {
+        initNMS(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+
         // Sets NOTIFICATION_HISTORY_ENABLED setting for calling process uid
         Settings.Secure.putIntForUser(mContext.getContentResolver(),
                 Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0, uid);
@@ -10090,7 +10105,7 @@
     }
 
     @Test
-    public void testHandleOnPackageRemoved_ClearsHistory() throws RemoteException {
+    public void testHandleOnPackageRemoved_ClearsHistory() throws Exception {
         // Enables Notification History setting
         setUpPrefsForHistory(mUid, true /* =enabled */);
 
@@ -13209,6 +13224,34 @@
     }
 
     @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void setDeviceEffectsApplier_succeeds() throws Exception {
+        initNMS(SystemService.PHASE_SYSTEM_SERVICES_READY);
+
+        mInternalService.setDeviceEffectsApplier(mock(DeviceEffectsApplier.class));
+        // No exception!
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void setDeviceEffectsApplier_tooLate_throws() throws Exception {
+        initNMS(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
+
+        assertThrows(IllegalStateException.class, () ->
+                mInternalService.setDeviceEffectsApplier(mock(DeviceEffectsApplier.class)));
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void setDeviceEffectsApplier_calledTwice_throws() throws Exception {
+        initNMS(SystemService.PHASE_SYSTEM_SERVICES_READY);
+
+        mInternalService.setDeviceEffectsApplier(mock(DeviceEffectsApplier.class));
+        assertThrows(IllegalStateException.class, () ->
+                mInternalService.setDeviceEffectsApplier(mock(DeviceEffectsApplier.class)));
+    }
+
+    @Test
     @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
     public void setNotificationPolicy_mappedToImplicitRule() throws RemoteException {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 526201f..670f9f6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -49,6 +49,7 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -195,7 +196,7 @@
         mAInfo.applicationInfo.flags = FLAG_SUSPENDED;
 
         when(mPackageManagerInternal.getSuspendingPackage(TEST_PACKAGE_NAME, TEST_USER_ID))
-                .thenReturn(PLATFORM_PACKAGE_NAME);
+                .thenReturn(UserPackage.of(TEST_USER_ID, PLATFORM_PACKAGE_NAME));
 
         // THEN calling intercept returns true
         assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null));
@@ -227,9 +228,10 @@
                 .setMessage("Test Message")
                 .setIcon(0x11110001)
                 .build();
+        UserPackage suspender = UserPackage.of(TEST_USER_ID, suspendingPackage);
         when(mPackageManagerInternal.getSuspendingPackage(TEST_PACKAGE_NAME, TEST_USER_ID))
-                .thenReturn(suspendingPackage);
-        when(mPackageManagerInternal.getSuspendedDialogInfo(TEST_PACKAGE_NAME, suspendingPackage,
+                .thenReturn(suspender);
+        when(mPackageManagerInternal.getSuspendedDialogInfo(TEST_PACKAGE_NAME, suspender,
                 TEST_USER_ID)).thenReturn(dialogInfo);
         return dialogInfo;
     }
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
index 12a57d5..c8cac8f 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -25,6 +25,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
@@ -142,7 +143,7 @@
     }
 
     /** During the transition Secondary Activity shrinks to the bottom right corner. */
-    @Presubmit
+    @FlakyTest(bugId = 315605409)
     @Test
     fun secondaryLayerShrinks() {
         flicker.assertLayers {