Merge "Make MyPackageMonitor#onPackageDataCleared multi-user aware" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 2047168..7317ecd 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -310,6 +310,8 @@
 aconfig_declarations {
     name: "android.os.flags-aconfig",
     package: "android.os",
+    exportable: true,
+    container: "system",
     srcs: ["core/java/android/os/*.aconfig"],
 }
 
@@ -326,6 +328,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "android.os.flags-aconfig-java-export",
+    aconfig_declarations: "android.os.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+    mode: "exported",
+}
+
 // VirtualDeviceManager
 cc_aconfig_library {
     name: "android.companion.virtualdevice.flags-aconfig-cc",
@@ -483,6 +492,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "android.content.res.flags-aconfig-java-host",
+    aconfig_declarations: "android.content.res.flags-aconfig",
+    host_supported: true,
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Media BetterTogether
 aconfig_declarations {
     name: "com.android.media.flags.bettertogether-aconfig",
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a28dc49..6fb6b0d 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1366,7 +1366,7 @@
   }
 
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public class IntentFactory {
-    method @NonNull public static android.content.Intent createCancelUiIntent(@NonNull android.os.IBinder, boolean, @NonNull String);
+    method @NonNull public static android.content.Intent createCancelUiIntent(@NonNull android.content.Context, @NonNull android.os.IBinder, boolean, @NonNull String);
     method @NonNull public static android.content.Intent createCredentialSelectorIntent(@NonNull android.content.Context, @NonNull android.credentials.selection.RequestInfo, @NonNull java.util.ArrayList<android.credentials.selection.ProviderData>, @NonNull java.util.ArrayList<android.credentials.selection.DisabledProviderData>, @NonNull android.os.ResultReceiver);
   }
 
@@ -1547,10 +1547,6 @@
     method public boolean isAllowBackgroundAuthentication();
   }
 
-  public abstract static class BiometricPrompt.AuthenticationCallback {
-    method @FlaggedApi("android.hardware.biometrics.face_background_authentication") public void onAuthenticationAcquired(int);
-  }
-
   public static class BiometricPrompt.Builder {
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean);
     method @FlaggedApi("android.multiuser.enable_biometrics_to_unlock_private_space") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean, boolean);
@@ -1569,7 +1565,6 @@
   }
 
   public class SensorProperties {
-    ctor @FlaggedApi("android.hardware.biometrics.face_background_authentication") public SensorProperties(int, int, @NonNull java.util.List<android.hardware.biometrics.SensorProperties.ComponentInfo>);
     method @NonNull public java.util.List<android.hardware.biometrics.SensorProperties.ComponentInfo> getComponentInfo();
     method public int getSensorId();
     method public int getSensorStrength();
@@ -1714,18 +1709,6 @@
 
 }
 
-package android.hardware.face {
-
-  @FlaggedApi("android.hardware.biometrics.face_background_authentication") public class FaceManager {
-    method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int);
-    method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @NonNull public java.util.List<android.hardware.face.FaceSensorProperties> getSensorProperties();
-  }
-
-  @FlaggedApi("android.hardware.biometrics.face_background_authentication") public class FaceSensorProperties extends android.hardware.biometrics.SensorProperties {
-  }
-
-}
-
 package android.hardware.fingerprint {
 
   @Deprecated public class FingerprintManager {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 41151c0..1d39186 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -40,6 +40,7 @@
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
 import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
+import static com.android.window.flags.Flags.activityWindowInfoFlag;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -63,6 +64,7 @@
 import android.app.servertransaction.ActivityRelaunchItem;
 import android.app.servertransaction.ActivityResultItem;
 import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionListenerController;
 import android.app.servertransaction.DestroyActivityItem;
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.PendingTransactionActions;
@@ -606,6 +608,8 @@
         Configuration overrideConfig;
         @NonNull
         private ActivityWindowInfo mActivityWindowInfo;
+        @Nullable
+        private ActivityWindowInfo mLastReportedActivityWindowInfo;
 
         // Used for consolidating configs before sending on to Activity.
         private final Configuration tmpConfig = new Configuration();
@@ -4180,6 +4184,9 @@
                 pendingActions.setRestoreInstanceState(true);
                 pendingActions.setCallOnPostCreate(true);
             }
+
+            // Trigger ActivityWindowInfo callback if first launch or change from relaunch.
+            handleActivityWindowInfoChanged(r);
         } else {
             // If there was an error, for any reason, tell the activity manager to stop us.
             ActivityClient.getInstance().finishActivity(r.token, Activity.RESULT_CANCELED,
@@ -4558,7 +4565,7 @@
     private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) {
         final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
         final PauseActivityItem pauseActivityItem = PauseActivityItem.obtain(r.token,
-                r.activity.isFinishing(), /* userLeaving */ true, r.activity.mConfigChangeFlags,
+                r.activity.isFinishing(), /* userLeaving */ true,
                 /* dontReport */ false, /* autoEnteringPip */ false);
         transaction.addTransactionItem(pauseActivityItem);
         executeTransaction(transaction);
@@ -5432,13 +5439,12 @@
 
     @Override
     public void handlePauseActivity(ActivityClientRecord r, boolean finished, boolean userLeaving,
-            int configChanges, boolean autoEnteringPip, PendingTransactionActions pendingActions,
+            boolean autoEnteringPip, PendingTransactionActions pendingActions,
             String reason) {
         if (userLeaving) {
             performUserLeavingActivity(r);
         }
 
-        r.activity.mConfigChangeFlags |= configChanges;
         if (autoEnteringPip) {
             // Set mIsInPictureInPictureMode earlier in case of auto-enter-pip, see also
             // {@link Activity#enterPictureInPictureMode(PictureInPictureParams)}.
@@ -5687,9 +5693,8 @@
     }
 
     @Override
-    public void handleStopActivity(ActivityClientRecord r, int configChanges,
+    public void handleStopActivity(ActivityClientRecord r,
             PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
-        r.activity.mConfigChangeFlags |= configChanges;
 
         final StopInfo stopInfo = new StopInfo();
         performStopActivityInner(r, stopInfo, true /* saveState */, finalStateRequest,
@@ -5859,11 +5864,10 @@
 
     /** Core implementation of activity destroy call. */
     void performDestroyActivity(ActivityClientRecord r, boolean finishing,
-            int configChanges, boolean getNonConfigInstance, String reason) {
+            boolean getNonConfigInstance, String reason) {
         Class<? extends Activity> activityClass;
         if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
         activityClass = r.activity.getClass();
-        r.activity.mConfigChangeFlags |= configChanges;
         if (finishing) {
             r.activity.mFinished = true;
         }
@@ -5928,9 +5932,9 @@
     }
 
     @Override
-    public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
+    public void handleDestroyActivity(ActivityClientRecord r, boolean finishing,
             boolean getNonConfigInstance, String reason) {
-        performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);
+        performDestroyActivity(r, finishing, getNonConfigInstance, reason);
         cleanUpPendingRemoveWindows(r, finishing);
         WindowManager wm = r.activity.getWindowManager();
         View v = r.activity.mDecor;
@@ -6130,7 +6134,7 @@
 
         r.activity.mChangingConfigurations = true;
 
-        handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
+        handleRelaunchActivityInner(r, tmp.pendingResults, tmp.pendingIntents,
                 pendingActions, tmp.startsNotResumed, tmp.overrideConfig, tmp.mActivityWindowInfo,
                 "handleRelaunchActivity");
     }
@@ -6199,7 +6203,7 @@
         executeTransaction(transaction);
     }
 
-    private void handleRelaunchActivityInner(@NonNull ActivityClientRecord r, int configChanges,
+    private void handleRelaunchActivityInner(@NonNull ActivityClientRecord r,
             @Nullable List<ResultInfo> pendingResults,
             @Nullable List<ReferrerIntent> pendingIntents,
             @NonNull PendingTransactionActions pendingActions, boolean startsNotResumed,
@@ -6215,7 +6219,7 @@
             callActivityOnStop(r, true /* saveState */, reason);
         }
 
-        handleDestroyActivity(r, false, configChanges, true, reason);
+        handleDestroyActivity(r, false /* finishing */, true /* getNonConfigInstance */, reason);
 
         r.activity = null;
         r.window = null;
@@ -6740,7 +6744,7 @@
         // Perform updates.
         r.overrideConfig = overrideConfig;
         r.mActivityWindowInfo = activityWindowInfo;
-        // TODO(b/287582673): notify on ActivityWindowInfo change
+
         final ViewRootImpl viewRoot = r.activity.mDecor != null
             ? r.activity.mDecor.getViewRootImpl() : null;
 
@@ -6763,6 +6767,22 @@
             viewRoot.updateConfiguration(displayId);
         }
         mSomeActivitiesChanged = true;
+
+        // Trigger ActivityWindowInfo callback if changed.
+        handleActivityWindowInfoChanged(r);
+    }
+
+    private void handleActivityWindowInfoChanged(@NonNull ActivityClientRecord r) {
+        if (!activityWindowInfoFlag()) {
+            return;
+        }
+        if (r.mActivityWindowInfo == null
+                || r.mActivityWindowInfo.equals(r.mLastReportedActivityWindowInfo)) {
+            return;
+        }
+        r.mLastReportedActivityWindowInfo = r.mActivityWindowInfo;
+        ClientTransactionListenerController.getInstance().onActivityWindowInfoChanged(r.token,
+                r.mActivityWindowInfo);
     }
 
     final void handleProfilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index b5b3669..01153c9 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -114,11 +114,11 @@
 
     /** Destroy the activity. */
     public abstract void handleDestroyActivity(@NonNull ActivityClientRecord r, boolean finishing,
-            int configChanges, boolean getNonConfigInstance, String reason);
+            boolean getNonConfigInstance, String reason);
 
     /** Pause the activity. */
     public abstract void handlePauseActivity(@NonNull ActivityClientRecord r, boolean finished,
-            boolean userLeaving, int configChanges, boolean autoEnteringPip,
+            boolean userLeaving, boolean autoEnteringPip,
             PendingTransactionActions pendingActions, String reason);
 
     /**
@@ -146,14 +146,13 @@
     /**
      * Stop the activity.
      * @param r Target activity record.
-     * @param configChanges Activity configuration changes.
      * @param pendingActions Pending actions to be used on this or later stages of activity
      *                       transaction.
      * @param finalStateRequest Flag indicating if this call is handling final lifecycle state
      *                          request for a transaction.
      * @param reason Reason for performing this operation.
      */
-    public abstract void handleStopActivity(@NonNull ActivityClientRecord r, int configChanges,
+    public abstract void handleStopActivity(@NonNull ActivityClientRecord r,
             PendingTransactionActions pendingActions, boolean finalStateRequest, String reason);
 
     /** Report that activity was stopped to server. */
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 1b19ecd..095cfc5 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -413,7 +413,7 @@
         if (localLOGV) Log.v(TAG, r.id + ": destroying");
         final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r);
         if (clientRecord != null) {
-            mActivityThread.performDestroyActivity(clientRecord, finish, 0 /* configChanges */,
+            mActivityThread.performDestroyActivity(clientRecord, finish,
                     false /* getNonConfigInstance */, "LocalActivityManager::performDestroy");
         }
         r.activity = null;
@@ -684,7 +684,7 @@
                 if (localLOGV) Log.v(TAG, r.id + ": no corresponding record");
                 continue;
             }
-            mActivityThread.performDestroyActivity(clientRecord, finishing, 0 /* configChanges */,
+            mActivityThread.performDestroyActivity(clientRecord, finishing,
                     false /* getNonConfigInstance */, "LocalActivityManager::dispatchDestroy");
         }
         mActivities.clear();
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index b2be27f..4a06f7d 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -248,7 +248,8 @@
     }
 
     /**
-     * Returns the default locale if specified, otherwise null
+     * Returns the locale the strings in values/strings.xml (the default strings in the directory
+     * with no locale qualifier) are in if specified, otherwise null
      *
      * @return The default Locale or null
      */
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index d49a254..283e21a 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -2052,10 +2052,12 @@
 
         /** Notification senders to prioritize for calls. One of:
          * PRIORITY_SENDERS_ANY, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED */
+        @PrioritySenders
         public final int priorityCallSenders;
 
         /** Notification senders to prioritize for messages. One of:
          * PRIORITY_SENDERS_ANY, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED */
+        @PrioritySenders
         public final int priorityMessageSenders;
 
         /**
@@ -2063,6 +2065,7 @@
          * {@link #CONVERSATION_SENDERS_NONE}, {@link #CONVERSATION_SENDERS_IMPORTANT},
          * {@link #CONVERSATION_SENDERS_ANYONE}.
          */
+        @ConversationSenders
         public final int priorityConversationSenders;
 
         /**
@@ -2630,16 +2633,19 @@
         }
 
         /** @hide **/
+        @PrioritySenders
         public int allowCallsFrom() {
             return priorityCallSenders;
         }
 
         /** @hide **/
+        @PrioritySenders
         public int allowMessagesFrom() {
             return priorityMessageSenders;
         }
 
         /** @hide **/
+        @ConversationSenders
         public int allowConversationsFrom() {
             return priorityConversationSenders;
         }
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 6255260..8b84f06 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -124,6 +124,32 @@
      */
     private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList());
 
+    private final ArrayMap<String, SharedLibraryAssets> mSharedLibAssetsMap =
+            new ArrayMap<>();
+
+    /**
+     * The internal function to register the resources paths of a package (e.g. a shared library).
+     * This will collect the package resources' paths from its ApplicationInfo and add them to all
+     * existing and future contexts while the application is running.
+     */
+    public void registerResourcePaths(@NonNull String uniqueId, @NonNull ApplicationInfo appInfo) {
+        SharedLibraryAssets sharedLibAssets = new SharedLibraryAssets(appInfo.sourceDir,
+                appInfo.splitSourceDirs, appInfo.sharedLibraryFiles,
+                appInfo.resourceDirs, appInfo.overlayPaths);
+
+        synchronized (mLock) {
+            if (mSharedLibAssetsMap.containsKey(uniqueId)) {
+                Slog.v(TAG, "Package resources' paths for uniqueId: " + uniqueId
+                        + " has already been registered, this is a no-op.");
+                return;
+            }
+            mSharedLibAssetsMap.put(uniqueId, sharedLibAssets);
+            appendLibAssetsLocked(sharedLibAssets.getAllAssetPaths());
+            Slog.v(TAG, "The following resources' paths have been added: "
+                    + Arrays.toString(sharedLibAssets.getAllAssetPaths()));
+        }
+    }
+
     private static class ApkKey {
         public final String path;
         public final boolean sharedLib;
@@ -278,6 +304,21 @@
     public ResourcesManager() {
     }
 
+    /**
+     * Inject a customized ResourcesManager instance for testing, return the old ResourcesManager
+     * instance.
+     */
+    @UnsupportedAppUsage
+    @VisibleForTesting
+    public static ResourcesManager setInstance(ResourcesManager resourcesManager) {
+        synchronized (ResourcesManager.class) {
+            ResourcesManager oldResourceManager = sResourcesManager;
+            sResourcesManager = resourcesManager;
+            return oldResourceManager;
+        }
+
+    }
+
     @UnsupportedAppUsage
     public static ResourcesManager getInstance() {
         synchronized (ResourcesManager.class) {
@@ -1480,6 +1521,56 @@
         }
     }
 
+    private void appendLibAssetsLocked(String[] libAssets) {
+        synchronized (mLock) {
+            // Record which ResourcesImpl need updating
+            // (and what ResourcesKey they should update to).
+            final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
+
+            final int implCount = mResourceImpls.size();
+            for (int i = 0; i < implCount; i++) {
+                final ResourcesKey key = mResourceImpls.keyAt(i);
+                final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
+                final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
+                if (impl == null) {
+                    Slog.w(TAG, "Found a ResourcesImpl which is null, skip it and continue to "
+                            + "append shared library assets for next ResourcesImpl.");
+                    continue;
+                }
+
+                var newDirs = new ArrayList<String>();
+                var dirsSet = new ArraySet<String>();
+                if (key.mLibDirs != null) {
+                    final int dirsLength = key.mLibDirs.length;
+                    for (int k = 0; k < dirsLength; k++) {
+                        newDirs.add(key.mLibDirs[k]);
+                        dirsSet.add(key.mLibDirs[k]);
+                    }
+                }
+                final int assetsLength = libAssets.length;
+                for (int j = 0; j < assetsLength; j++) {
+                    if (dirsSet.add(libAssets[j])) {
+                        newDirs.add(libAssets[j]);
+                    }
+                }
+                String[] newLibAssets = newDirs.toArray(new String[0]);
+                if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
+                    updatedResourceKeys.put(impl, new ResourcesKey(
+                            key.mResDir,
+                            key.mSplitResDirs,
+                            key.mOverlayPaths,
+                            newLibAssets,
+                            key.mDisplayId,
+                            key.mOverrideConfiguration,
+                            key.mCompatInfo,
+                            key.mLoaders));
+                }
+            }
+
+            redirectResourcesToNewImplLocked(updatedResourceKeys);
+        }
+    }
+
     private void applyNewResourceDirsLocked(@Nullable final String[] oldSourceDirs,
             @NonNull final ApplicationInfo appInfo) {
         try {
@@ -1689,4 +1780,50 @@
             }
         }
     }
+
+    public static class SharedLibraryAssets{
+        private final String[] mAssetPaths;
+
+        SharedLibraryAssets(String sourceDir, String[] splitSourceDirs, String[] sharedLibraryFiles,
+                String[] resourceDirs, String[] overlayPaths) {
+            mAssetPaths = collectAssetPaths(sourceDir, splitSourceDirs, sharedLibraryFiles,
+                    resourceDirs, overlayPaths);
+        }
+
+        private @NonNull String[] collectAssetPaths(String sourceDir, String[] splitSourceDirs,
+                String[] sharedLibraryFiles, String[] resourceDirs, String[] overlayPaths) {
+            final String[][] inputLists = {
+                    splitSourceDirs, sharedLibraryFiles, resourceDirs, overlayPaths
+            };
+
+            final ArraySet<String> assetPathSet = new ArraySet<>();
+            final List<String> assetPathList = new ArrayList<>();
+            if (sourceDir != null) {
+                assetPathSet.add(sourceDir);
+                assetPathList.add(sourceDir);
+            }
+
+            for (int i = 0; i < inputLists.length; i++) {
+                if (inputLists[i] != null) {
+                    for (int j = 0; j < inputLists[i].length; j++) {
+                        if (assetPathSet.add(inputLists[i][j])) {
+                            assetPathList.add(inputLists[i][j]);
+                        }
+                    }
+                }
+            }
+            return assetPathList.toArray(new String[0]);
+        }
+
+        /**
+         * @return all the asset paths of this collected in this class.
+         */
+        public @NonNull String[] getAllAssetPaths() {
+            return mAssetPaths;
+        }
+    }
+
+    public @NonNull ArrayMap<String, SharedLibraryAssets> getSharedLibAssetsMap() {
+        return new ArrayMap<>(mSharedLibAssetsMap);
+    }
 }
diff --git a/core/java/android/app/admin/AccountTypePolicyKey.java b/core/java/android/app/admin/AccountTypePolicyKey.java
index d81eb20..51f3137 100644
--- a/core/java/android/app/admin/AccountTypePolicyKey.java
+++ b/core/java/android/app/admin/AccountTypePolicyKey.java
@@ -19,12 +19,12 @@
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_ACCOUNT_TYPE;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
 import android.os.Bundle;
 import android.os.Parcel;
 
@@ -54,7 +54,7 @@
     @TestApi
     public AccountTypePolicyKey(@NonNull String key, @NonNull String accountType) {
         super(key);
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             PolicySizeVerifier.enforceMaxStringLength(accountType, "accountType");
         }
         mAccountType = Objects.requireNonNull((accountType));
diff --git a/core/java/android/app/admin/BundlePolicyValue.java b/core/java/android/app/admin/BundlePolicyValue.java
index cc5e75f..4f70604 100644
--- a/core/java/android/app/admin/BundlePolicyValue.java
+++ b/core/java/android/app/admin/BundlePolicyValue.java
@@ -16,10 +16,9 @@
 
 package android.app.admin;
 
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
 import android.os.Bundle;
 import android.os.Parcel;
 
@@ -32,7 +31,7 @@
 
     public BundlePolicyValue(Bundle value) {
         super(value);
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             PolicySizeVerifier.enforceMaxParcelableFieldsLength(value);
         }
     }
diff --git a/core/java/android/app/admin/ComponentNamePolicyValue.java b/core/java/android/app/admin/ComponentNamePolicyValue.java
index 4d36195..a957dbf 100644
--- a/core/java/android/app/admin/ComponentNamePolicyValue.java
+++ b/core/java/android/app/admin/ComponentNamePolicyValue.java
@@ -16,10 +16,9 @@
 
 package android.app.admin;
 
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
 import android.content.ComponentName;
 import android.os.Parcel;
 
@@ -32,7 +31,7 @@
 
     public ComponentNamePolicyValue(@NonNull ComponentName value) {
         super(value);
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             PolicySizeVerifier.enforceMaxComponentNameLength(value);
         }
     }
diff --git a/core/java/android/app/admin/IntentFilterPolicyKey.java b/core/java/android/app/admin/IntentFilterPolicyKey.java
index de7ff9f..63c3a4cb 100644
--- a/core/java/android/app/admin/IntentFilterPolicyKey.java
+++ b/core/java/android/app/admin/IntentFilterPolicyKey.java
@@ -19,12 +19,12 @@
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_INTENT_FILTER;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
 import android.content.IntentFilter;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -60,7 +60,7 @@
     @TestApi
     public IntentFilterPolicyKey(@NonNull String identifier, @NonNull IntentFilter filter) {
         super(identifier);
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             PolicySizeVerifier.enforceMaxParcelableFieldsLength(filter);
         }
         mFilter = Objects.requireNonNull(filter);
diff --git a/core/java/android/app/admin/LockTaskPolicy.java b/core/java/android/app/admin/LockTaskPolicy.java
index 9d6ce24..a36ea05 100644
--- a/core/java/android/app/admin/LockTaskPolicy.java
+++ b/core/java/android/app/admin/LockTaskPolicy.java
@@ -16,11 +16,10 @@
 
 package android.app.admin;
 
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.app.admin.flags.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -136,7 +135,7 @@
     }
 
     private void setPackagesInternal(Set<String> packages) {
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             for (String p : packages) {
                 PolicySizeVerifier.enforceMaxPackageNameLength(p);
             }
diff --git a/core/java/android/app/admin/PackagePermissionPolicyKey.java b/core/java/android/app/admin/PackagePermissionPolicyKey.java
index 2241fdd..389585f 100644
--- a/core/java/android/app/admin/PackagePermissionPolicyKey.java
+++ b/core/java/android/app/admin/PackagePermissionPolicyKey.java
@@ -20,12 +20,12 @@
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_PERMISSION_NAME;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -59,7 +59,7 @@
     public PackagePermissionPolicyKey(@NonNull String identifier, @NonNull String packageName,
             @NonNull String permissionName) {
         super(identifier);
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
             PolicySizeVerifier.enforceMaxStringLength(permissionName, "permissionName");
         }
diff --git a/core/java/android/app/admin/PackagePolicyKey.java b/core/java/android/app/admin/PackagePolicyKey.java
index 2ea17a1..68dc797 100644
--- a/core/java/android/app/admin/PackagePolicyKey.java
+++ b/core/java/android/app/admin/PackagePolicyKey.java
@@ -19,12 +19,12 @@
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_PACKAGE_NAME;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY;
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -55,7 +55,7 @@
     @TestApi
     public PackagePolicyKey(@NonNull String key, @NonNull String packageName) {
         super(key);
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
         }
         mPackageName = Objects.requireNonNull((packageName));
diff --git a/core/java/android/app/admin/StringPolicyValue.java b/core/java/android/app/admin/StringPolicyValue.java
index f4d4adc..8995c0f 100644
--- a/core/java/android/app/admin/StringPolicyValue.java
+++ b/core/java/android/app/admin/StringPolicyValue.java
@@ -16,10 +16,9 @@
 
 package android.app.admin;
 
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
 import android.os.Parcel;
 
 import java.util.Objects;
@@ -31,7 +30,7 @@
 
     public StringPolicyValue(@NonNull String value) {
         super(value);
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             PolicySizeVerifier.enforceMaxStringLength(value, "policyValue");
         }
     }
diff --git a/core/java/android/app/admin/StringSetPolicyValue.java b/core/java/android/app/admin/StringSetPolicyValue.java
index 82fe761..f37dfee 100644
--- a/core/java/android/app/admin/StringSetPolicyValue.java
+++ b/core/java/android/app/admin/StringSetPolicyValue.java
@@ -16,10 +16,9 @@
 
 package android.app.admin;
 
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
 import android.os.Parcel;
 
 import java.util.HashSet;
@@ -33,7 +32,7 @@
 
     public StringSetPolicyValue(@NonNull Set<String> value) {
         super(value);
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             for (String str : value) {
                 PolicySizeVerifier.enforceMaxStringLength(str, "policyValue");
             }
diff --git a/core/java/android/app/admin/UserRestrictionPolicyKey.java b/core/java/android/app/admin/UserRestrictionPolicyKey.java
index d69a5f0..ee90ccd 100644
--- a/core/java/android/app/admin/UserRestrictionPolicyKey.java
+++ b/core/java/android/app/admin/UserRestrictionPolicyKey.java
@@ -17,11 +17,11 @@
 package android.app.admin;
 
 import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
 import android.os.Bundle;
 import android.os.Parcel;
 
@@ -45,7 +45,7 @@
     @TestApi
     public UserRestrictionPolicyKey(@NonNull String identifier, @NonNull String restriction) {
         super(identifier);
-        if (devicePolicySizeTrackingEnabled()) {
+        if (Flags.devicePolicySizeTrackingInternalEnabled()) {
             PolicySizeVerifier.enforceMaxStringLength(restriction, "restriction");
         }
         mRestriction = Objects.requireNonNull(restriction);
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index e1a6913..1927019 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -10,7 +10,14 @@
 flag {
   name: "device_policy_size_tracking_enabled"
   namespace: "enterprise"
-  description: "Add feature to track the total policy size and have a max threshold."
+  description: "Add feature to track the total policy size and have a max threshold - public API changes"
+  bug: "281543351"
+}
+
+flag {
+  name: "device_policy_size_tracking_internal_enabled"
+  namespace: "enterprise"
+  description: "Add feature to track the total policy size and have a max threshold - internal changes"
   bug: "281543351"
 }
 
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 1a8136e..7383d07 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -16,16 +16,24 @@
 
 package android.app.servertransaction;
 
+import static com.android.window.flags.Flags.activityWindowInfoFlag;
 import static com.android.window.flags.Flags.bundleClientTransactionFlag;
 
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.NonNull;
+import android.app.Activity;
 import android.app.ActivityThread;
 import android.hardware.display.DisplayManagerGlobal;
+import android.os.IBinder;
+import android.util.ArraySet;
+import android.window.ActivityWindowInfo;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.function.BiConsumer;
+
 /**
  * Singleton controller to manage listeners to individual {@link ClientTransaction}.
  *
@@ -35,8 +43,14 @@
 
     private static ClientTransactionListenerController sController;
 
+    private final Object mLock = new Object();
     private final DisplayManagerGlobal mDisplayManager;
 
+    /** Listeners registered via {@link #registerActivityWindowInfoChangedListener(BiConsumer)}. */
+    @GuardedBy("mLock")
+    private final ArraySet<BiConsumer<IBinder, ActivityWindowInfo>>
+            mActivityWindowInfoChangedListeners = new ArraySet<>();
+
     /** Gets the singleton controller. */
     @NonNull
     public static ClientTransactionListenerController getInstance() {
@@ -62,6 +76,57 @@
     }
 
     /**
+     * Registers to listen on activity {@link ActivityWindowInfo} change.
+     * The listener will be invoked with two parameters: {@link Activity#getActivityToken()} and
+     * {@link ActivityWindowInfo}.
+     */
+    public void registerActivityWindowInfoChangedListener(
+            @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
+        if (!activityWindowInfoFlag()) {
+            return;
+        }
+        synchronized (mLock) {
+            mActivityWindowInfoChangedListeners.add(listener);
+        }
+    }
+
+    /**
+     * Unregisters the listener that was previously registered via
+     * {@link #registerActivityWindowInfoChangedListener(BiConsumer)}
+     */
+    public void unregisterActivityWindowInfoChangedListener(
+            @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
+        if (!activityWindowInfoFlag()) {
+            return;
+        }
+        synchronized (mLock) {
+            mActivityWindowInfoChangedListeners.remove(listener);
+        }
+    }
+
+    /**
+     * Called when receives a {@link ClientTransaction} that is updating an activity's
+     * {@link ActivityWindowInfo}.
+     */
+    public void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
+            @NonNull ActivityWindowInfo activityWindowInfo) {
+        if (!activityWindowInfoFlag()) {
+            return;
+        }
+        final Object[] activityWindowInfoChangedListeners;
+        synchronized (mLock) {
+            if (mActivityWindowInfoChangedListeners.isEmpty()) {
+                return;
+            }
+            activityWindowInfoChangedListeners = mActivityWindowInfoChangedListeners.toArray();
+        }
+        for (Object activityWindowInfoChangedListener : activityWindowInfoChangedListeners) {
+            ((BiConsumer<IBinder, ActivityWindowInfo>) activityWindowInfoChangedListener)
+                    .accept(activityToken, activityWindowInfo);
+        }
+    }
+
+    /**
      * Called when receives a {@link ClientTransaction} that is updating display-related
      * window configuration.
      */
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
index f9cf075..b0213d7 100644
--- a/core/java/android/app/servertransaction/DestroyActivityItem.java
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -33,7 +33,6 @@
 public class DestroyActivityItem extends ActivityLifecycleItem {
 
     private boolean mFinished;
-    private int mConfigChanges;
 
     @Override
     public void preExecute(@NonNull ClientTransactionHandler client) {
@@ -44,7 +43,7 @@
     public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
             @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
-        client.handleDestroyActivity(r, mFinished, mConfigChanges,
+        client.handleDestroyActivity(r, mFinished,
                 false /* getNonConfigInstance */, "DestroyActivityItem");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -67,15 +66,13 @@
 
     /** Obtain an instance initialized with provided params. */
     @NonNull
-    public static DestroyActivityItem obtain(@NonNull IBinder activityToken, boolean finished,
-            int configChanges) {
+    public static DestroyActivityItem obtain(@NonNull IBinder activityToken, boolean finished) {
         DestroyActivityItem instance = ObjectPool.obtain(DestroyActivityItem.class);
         if (instance == null) {
             instance = new DestroyActivityItem();
         }
         instance.setActivityToken(activityToken);
         instance.mFinished = finished;
-        instance.mConfigChanges = configChanges;
 
         return instance;
     }
@@ -84,7 +81,6 @@
     public void recycle() {
         super.recycle();
         mFinished = false;
-        mConfigChanges = 0;
         ObjectPool.recycle(this);
     }
 
@@ -95,14 +91,12 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
         dest.writeBoolean(mFinished);
-        dest.writeInt(mConfigChanges);
     }
 
     /** Read from Parcel. */
     private DestroyActivityItem(@NonNull Parcel in) {
         super(in);
         mFinished = in.readBoolean();
-        mConfigChanges = in.readInt();
     }
 
     public static final @NonNull Creator<DestroyActivityItem> CREATOR = new Creator<>() {
@@ -124,7 +118,7 @@
             return false;
         }
         final DestroyActivityItem other = (DestroyActivityItem) o;
-        return mFinished == other.mFinished && mConfigChanges == other.mConfigChanges;
+        return mFinished == other.mFinished;
     }
 
     @Override
@@ -132,14 +126,12 @@
         int result = 17;
         result = 31 * result + super.hashCode();
         result = 31 * result + (mFinished ? 1 : 0);
-        result = 31 * result + mConfigChanges;
         return result;
     }
 
     @Override
     public String toString() {
         return "DestroyActivityItem{" + super.toString()
-                + ",finished=" + mFinished
-                + ",mConfigChanges=" + mConfigChanges + "}";
+                + ",finished=" + mFinished + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java
index 8f1e90b..d230284 100644
--- a/core/java/android/app/servertransaction/PauseActivityItem.java
+++ b/core/java/android/app/servertransaction/PauseActivityItem.java
@@ -37,7 +37,6 @@
 
     private boolean mFinished;
     private boolean mUserLeaving;
-    private int mConfigChanges;
     private boolean mDontReport;
     private boolean mAutoEnteringPip;
 
@@ -45,7 +44,7 @@
     public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
             @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
-        client.handlePauseActivity(r, mFinished, mUserLeaving, mConfigChanges, mAutoEnteringPip,
+        client.handlePauseActivity(r, mFinished, mUserLeaving, mAutoEnteringPip,
                 pendingActions, "PAUSE_ACTIVITY_ITEM");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -72,7 +71,7 @@
     /** Obtain an instance initialized with provided params. */
     @NonNull
     public static PauseActivityItem obtain(@NonNull IBinder activityToken, boolean finished,
-            boolean userLeaving, int configChanges, boolean dontReport, boolean autoEnteringPip) {
+            boolean userLeaving, boolean dontReport, boolean autoEnteringPip) {
         PauseActivityItem instance = ObjectPool.obtain(PauseActivityItem.class);
         if (instance == null) {
             instance = new PauseActivityItem();
@@ -80,7 +79,6 @@
         instance.setActivityToken(activityToken);
         instance.mFinished = finished;
         instance.mUserLeaving = userLeaving;
-        instance.mConfigChanges = configChanges;
         instance.mDontReport = dontReport;
         instance.mAutoEnteringPip = autoEnteringPip;
 
@@ -91,7 +89,7 @@
     @NonNull
     public static PauseActivityItem obtain(@NonNull IBinder activityToken) {
         return obtain(activityToken, false /* finished */, false /* userLeaving */,
-                0 /* configChanges */, true /* dontReport */, false /* autoEnteringPip*/);
+                true /* dontReport */, false /* autoEnteringPip*/);
     }
 
     @Override
@@ -99,7 +97,6 @@
         super.recycle();
         mFinished = false;
         mUserLeaving = false;
-        mConfigChanges = 0;
         mDontReport = false;
         mAutoEnteringPip = false;
         ObjectPool.recycle(this);
@@ -113,7 +110,6 @@
         super.writeToParcel(dest, flags);
         dest.writeBoolean(mFinished);
         dest.writeBoolean(mUserLeaving);
-        dest.writeInt(mConfigChanges);
         dest.writeBoolean(mDontReport);
         dest.writeBoolean(mAutoEnteringPip);
     }
@@ -123,7 +119,6 @@
         super(in);
         mFinished = in.readBoolean();
         mUserLeaving = in.readBoolean();
-        mConfigChanges = in.readInt();
         mDontReport = in.readBoolean();
         mAutoEnteringPip = in.readBoolean();
     }
@@ -148,7 +143,7 @@
         }
         final PauseActivityItem other = (PauseActivityItem) o;
         return mFinished == other.mFinished && mUserLeaving == other.mUserLeaving
-                && mConfigChanges == other.mConfigChanges && mDontReport == other.mDontReport
+                && mDontReport == other.mDontReport
                 && mAutoEnteringPip == other.mAutoEnteringPip;
     }
 
@@ -158,7 +153,6 @@
         result = 31 * result + super.hashCode();
         result = 31 * result + (mFinished ? 1 : 0);
         result = 31 * result + (mUserLeaving ? 1 : 0);
-        result = 31 * result + mConfigChanges;
         result = 31 * result + (mDontReport ? 1 : 0);
         result = 31 * result + (mAutoEnteringPip ? 1 : 0);
         return result;
@@ -169,7 +163,6 @@
         return "PauseActivityItem{" + super.toString()
                 + ",finished=" + mFinished
                 + ",userLeaving=" + mUserLeaving
-                + ",configChanges=" + mConfigChanges
                 + ",dontReport=" + mDontReport
                 + ",autoEnteringPip=" + mAutoEnteringPip + "}";
     }
diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java
index b8ce52d..def7b3f 100644
--- a/core/java/android/app/servertransaction/StopActivityItem.java
+++ b/core/java/android/app/servertransaction/StopActivityItem.java
@@ -19,7 +19,6 @@
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.os.IBinder;
@@ -34,13 +33,11 @@
 
     private static final String TAG = "StopActivityItem";
 
-    private int mConfigChanges;
-
     @Override
     public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
             @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
-        client.handleStopActivity(r, mConfigChanges, pendingActions,
+        client.handleStopActivity(r, pendingActions,
                 true /* finalStateRequest */, "STOP_ACTIVITY_ITEM");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -63,16 +60,14 @@
     /**
      * Obtain an instance initialized with provided params.
      * @param activityToken the activity that stops.
-     * @param configChanges Configuration pieces that changed.
      */
     @NonNull
-    public static StopActivityItem obtain(@NonNull IBinder activityToken, int configChanges) {
+    public static StopActivityItem obtain(@NonNull IBinder activityToken) {
         StopActivityItem instance = ObjectPool.obtain(StopActivityItem.class);
         if (instance == null) {
             instance = new StopActivityItem();
         }
         instance.setActivityToken(activityToken);
-        instance.mConfigChanges = configChanges;
 
         return instance;
     }
@@ -80,23 +75,14 @@
     @Override
     public void recycle() {
         super.recycle();
-        mConfigChanges = 0;
         ObjectPool.recycle(this);
     }
 
     // Parcelable implementation
 
-    /** Write to Parcel. */
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        super.writeToParcel(dest, flags);
-        dest.writeInt(mConfigChanges);
-    }
-
     /** Read from Parcel. */
     private StopActivityItem(@NonNull Parcel in) {
         super(in);
-        mConfigChanges = in.readInt();
     }
 
     public static final @NonNull Creator<StopActivityItem> CREATOR = new Creator<>() {
@@ -110,28 +96,7 @@
     };
 
     @Override
-    public boolean equals(@Nullable Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (!super.equals(o)) {
-            return false;
-        }
-        final StopActivityItem other = (StopActivityItem) o;
-        return mConfigChanges == other.mConfigChanges;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = 17;
-        result = 31 * result + super.hashCode();
-        result = 31 * result + mConfigChanges;
-        return result;
-    }
-
-    @Override
     public String toString() {
-        return "StopActivityItem{" + super.toString()
-                + ",configChanges=" + mConfigChanges + "}";
+        return "StopActivityItem{" + super.toString() + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index fa73c99..c837191 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -334,18 +334,18 @@
                     break;
                 case ON_PAUSE:
                     mTransactionHandler.handlePauseActivity(r, false /* finished */,
-                            false /* userLeaving */, 0 /* configChanges */,
+                            false /* userLeaving */,
                             false /* autoEnteringPip */, mPendingActions,
                             "LIFECYCLER_PAUSE_ACTIVITY");
                     break;
                 case ON_STOP:
-                    mTransactionHandler.handleStopActivity(r, 0 /* configChanges */,
+                    mTransactionHandler.handleStopActivity(r,
                             mPendingActions, false /* finalStateRequest */,
                             "LIFECYCLER_STOP_ACTIVITY");
                     break;
                 case ON_DESTROY:
                     mTransactionHandler.handleDestroyActivity(r, false /* finishing */,
-                            0 /* configChanges */, false /* getNonConfigInstance */,
+                            false /* getNonConfigInstance */,
                             "performLifecycleSequence. cycling to:" + path.get(size - 1));
                     break;
                 case ON_RESTART:
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index 475c6fb..710261a 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -200,7 +200,7 @@
                 lifecycleItem = PauseActivityItem.obtain(r.token);
                 break;
             case ON_STOP:
-                lifecycleItem = StopActivityItem.obtain(r.token, 0 /* configChanges */);
+                lifecycleItem = StopActivityItem.obtain(r.token);
                 break;
             default:
                 lifecycleItem = ResumeActivityItem.obtain(r.token, false /* isForward */,
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 3e9f260..8a3a3ad 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -112,6 +112,12 @@
     /**
      * Indicates that this user is disabled.
      *
+     * <p> This is currently used to indicate that a Managed Profile, when created via
+     * DevicePolicyManager, has not yet been provisioned; once the DPC provisions it, a DPM call
+     * will manually set it to enabled.
+     *
+     * <p>Users that are slated for deletion are also generally set to disabled.
+     *
      * <p>Note: If an ephemeral user is disabled, it shouldn't be later re-enabled. Ephemeral users
      * are disabled as their removal is in progress to indicate that they shouldn't be re-entered.
      */
@@ -398,6 +404,7 @@
         return UserManager.isUserTypePrivateProfile(userType);
     }
 
+    /** See {@link #FLAG_DISABLED}*/
     @UnsupportedAppUsage
     public boolean isEnabled() {
         return (flags & FLAG_DISABLED) != FLAG_DISABLED;
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 5e9d8f0..610057b 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -208,6 +208,14 @@
 }
 
 flag {
+    name: "restrict_nonpreloads_system_shareduids"
+    namespace: "package_manager_service"
+    description: "Feature flag to restrict apps from joining system shared uids"
+    bug: "308573169"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "min_target_sdk_24"
     namespace: "responsible_apis"
     description: "Feature flag to bump min target sdk to 24"
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index d259e97..273e40a 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -471,6 +471,16 @@
         return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/);
     }
 
+    /**
+     * @hide
+     */
+    public void addSharedLibraryPaths(@NonNull String[] paths) {
+        final int length = paths.length;
+        for (int i = 0; i < length; i++) {
+            addAssetPathInternal(paths[i], false, true);
+        }
+    }
+
     private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) {
         Objects.requireNonNull(path, "path");
         synchronized (this) {
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 7fba3e8..1f5f88f 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -43,6 +43,7 @@
 import android.annotation.StyleableRes;
 import android.annotation.XmlRes;
 import android.app.Application;
+import android.app.ResourcesManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -2854,6 +2855,11 @@
     @FlaggedApi(android.content.res.Flags.FLAG_REGISTER_RESOURCE_PATHS)
     public static void registerResourcePaths(@NonNull String uniqueId,
             @NonNull ApplicationInfo appInfo) {
-        throw new UnsupportedOperationException("The implementation has not been done yet.");
+        if (Flags.registerResourcePaths()) {
+            ResourcesManager.getInstance().registerResourcePaths(uniqueId, appInfo);
+        } else {
+            throw new UnsupportedOperationException("Flag " + Flags.FLAG_REGISTER_RESOURCE_PATHS
+                    + " is disabled.");
+        }
     }
 }
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 079c2c1..8d045aa 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -29,6 +29,7 @@
 import android.annotation.StyleableRes;
 import android.app.LocaleConfig;
 import android.app.ResourcesManager;
+import android.app.ResourcesManager.SharedLibraryAssets;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.Config;
@@ -47,6 +48,7 @@
 import android.os.LocaleList;
 import android.os.ParcelFileDescriptor;
 import android.os.Trace;
+import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -197,6 +199,14 @@
     public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
             @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
         mAssets = assets;
+        if (Flags.registerResourcePaths()) {
+            ArrayMap<String, SharedLibraryAssets> sharedLibMap =
+                    ResourcesManager.getInstance().getSharedLibAssetsMap();
+            final int size = sharedLibMap.size();
+            for (int i = 0; i < size; i++) {
+                assets.addSharedLibraryPaths(sharedLibMap.valueAt(i).getAllAssetPaths());
+            }
+        }
         mMetrics.setToDefaults();
         mDisplayAdjustments = displayAdjustments;
         mConfiguration.setToDefaults();
diff --git a/core/java/android/credentials/selection/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java
index 4b0fa6d..79fba9b 100644
--- a/core/java/android/credentials/selection/IntentFactory.java
+++ b/core/java/android/credentials/selection/IntentFactory.java
@@ -80,17 +80,7 @@
             ArrayList<DisabledProviderData> disabledProviderDataList,
             @NonNull ResultReceiver resultReceiver) {
         Intent intent = new Intent();
-        ComponentName componentName =
-                ComponentName.unflattenFromString(
-                        Resources.getSystem()
-                                .getString(
-                                        com.android.internal.R.string
-                                                .config_credentialManagerDialogComponent));
-        ComponentName oemOverrideComponentName = getOemOverrideComponentName(context);
-        if (oemOverrideComponentName != null) {
-            componentName = oemOverrideComponentName;
-        }
-        intent.setComponent(componentName);
+        setCredentialSelectorUiComponentName(context, intent);
         intent.putParcelableArrayListExtra(
                 ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
         intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
@@ -100,6 +90,24 @@
         return intent;
     }
 
+    private static void setCredentialSelectorUiComponentName(@NonNull Context context,
+            @NonNull Intent intent) {
+        if (configurableSelectorUiEnabled()) {
+            ComponentName componentName = getOemOverrideComponentName(context);
+            if (componentName == null) {
+                componentName = ComponentName.unflattenFromString(Resources.getSystem().getString(
+                        com.android.internal.R.string
+                                .config_fallbackCredentialManagerDialogComponent));
+            }
+            intent.setComponent(componentName);
+        } else {
+            ComponentName componentName = ComponentName.unflattenFromString(Resources.getSystem()
+                    .getString(com.android.internal.R.string
+                            .config_fallbackCredentialManagerDialogComponent));
+            intent.setComponent(componentName);
+        }
+    }
+
     /**
      * Returns null if there is not an enabled and valid oem override component. It means the
      * default platform UI component name should be used instead.
@@ -107,44 +115,39 @@
     @Nullable
     private static ComponentName getOemOverrideComponentName(@NonNull Context context) {
         ComponentName result = null;
-        if (configurableSelectorUiEnabled()) {
-            if (Resources.getSystem().getBoolean(
-                    com.android.internal.R.bool.config_enableOemCredentialManagerDialogComponent)) {
-                String oemComponentString =
-                        Resources.getSystem()
-                                .getString(
-                                        com.android.internal.R.string
-                                                .config_oemCredentialManagerDialogComponent);
-                if (!TextUtils.isEmpty(oemComponentString)) {
-                    ComponentName oemComponentName = ComponentName.unflattenFromString(
-                            oemComponentString);
-                    if (oemComponentName != null) {
-                        try {
-                            ActivityInfo info = context.getPackageManager().getActivityInfo(
-                                    oemComponentName,
-                                    PackageManager.ComponentInfoFlags.of(
-                                            PackageManager.MATCH_SYSTEM_ONLY));
-                            if (info.enabled && info.exported) {
-                                Slog.i(TAG,
-                                        "Found enabled oem CredMan UI component."
-                                                + oemComponentString);
-                                result = oemComponentName;
-                            } else {
-                                Slog.i(TAG,
-                                        "Found enabled oem CredMan UI component but it was not "
-                                                + "enabled.");
-                            }
-                        } catch (PackageManager.NameNotFoundException e) {
-                            Slog.i(TAG, "Unable to find oem CredMan UI component: "
-                                    + oemComponentString + ".");
-                        }
+        String oemComponentString =
+                Resources.getSystem()
+                        .getString(
+                                com.android.internal.R.string
+                                        .config_oemCredentialManagerDialogComponent);
+        if (!TextUtils.isEmpty(oemComponentString)) {
+            ComponentName oemComponentName = ComponentName.unflattenFromString(
+                    oemComponentString);
+            if (oemComponentName != null) {
+                try {
+                    ActivityInfo info = context.getPackageManager().getActivityInfo(
+                            oemComponentName,
+                            PackageManager.ComponentInfoFlags.of(
+                                    PackageManager.MATCH_SYSTEM_ONLY));
+                    if (info.enabled && info.exported) {
+                        Slog.i(TAG,
+                                "Found enabled oem CredMan UI component."
+                                        + oemComponentString);
+                        result = oemComponentName;
                     } else {
-                        Slog.i(TAG, "Invalid OEM ComponentName format.");
+                        Slog.i(TAG,
+                                "Found enabled oem CredMan UI component but it was not "
+                                        + "enabled.");
                     }
-                } else {
-                    Slog.i(TAG, "Invalid empty OEM component name.");
+                } catch (PackageManager.NameNotFoundException e) {
+                    Slog.i(TAG, "Unable to find oem CredMan UI component: "
+                            + oemComponentString + ".");
                 }
+            } else {
+                Slog.i(TAG, "Invalid OEM ComponentName format.");
             }
+        } else {
+            Slog.i(TAG, "Invalid empty OEM component name.");
         }
         return result;
     }
@@ -186,16 +189,11 @@
      * Creates an Intent that cancels any UI matching the given request token id.
      */
     @NonNull
-    public static Intent createCancelUiIntent(@NonNull IBinder requestToken,
-            boolean shouldShowCancellationUi, @NonNull String appPackageName) {
+    public static Intent createCancelUiIntent(@NonNull Context context,
+            @NonNull IBinder requestToken, boolean shouldShowCancellationUi,
+            @NonNull String appPackageName) {
         Intent intent = new Intent();
-        ComponentName componentName =
-                ComponentName.unflattenFromString(
-                        Resources.getSystem()
-                                .getString(
-                                        com.android.internal.R.string
-                                                .config_credentialManagerDialogComponent));
-        intent.setComponent(componentName);
+        setCredentialSelectorUiComponentName(context, intent);
         intent.putExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
                 new CancelSelectionRequest(new RequestToken(requestToken), shouldShowCancellationUi,
                         appPackageName));
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index d9d4305..0208fed 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -23,7 +23,6 @@
 import static android.hardware.biometrics.BiometricManager.Authenticators;
 import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT;
 import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
 import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT;
 import static android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE;
 
@@ -1128,8 +1127,6 @@
          * @hide
          */
         @Override
-        @TestApi
-        @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
         public void onAuthenticationAcquired(int acquireInfo) {}
 
         /**
diff --git a/core/java/android/hardware/biometrics/SensorProperties.java b/core/java/android/hardware/biometrics/SensorProperties.java
index 16f71414..3b9cad4 100644
--- a/core/java/android/hardware/biometrics/SensorProperties.java
+++ b/core/java/android/hardware/biometrics/SensorProperties.java
@@ -16,9 +16,6 @@
 
 package android.hardware.biometrics;
 
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
-
-import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.TestApi;
@@ -144,10 +141,8 @@
     /**
      * @hide
      */
-    @TestApi
-    @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
     public SensorProperties(int sensorId, @Strength int sensorStrength,
-            @NonNull List<ComponentInfo> componentInfo) {
+            List<ComponentInfo> componentInfo) {
         mSensorId = sensorId;
         mSensorStrength = sensorStrength;
         mComponentInfo = componentInfo;
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 57b437f..dc8f4b4 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -3521,7 +3521,7 @@
      * <p>When the key is present, only a PRIVATE/YUV output of the specified size is guaranteed
      * to be supported by the camera HAL in the secure camera mode. Any other format or
      * resolutions might not be supported. Use
-     * {@link CameraManager#isSessionConfigurationWithParametersSupported }
+     * {@link CameraDevice#isSessionConfigurationSupported }
      * API to query if a secure session configuration is supported if the device supports this
      * API.</p>
      * <p>If this key returns null on a device with SECURE_IMAGE_DATA capability, the application
@@ -5046,18 +5046,18 @@
 
     /**
      * <p>The version of the session configuration query
-     * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }
+     * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
      * API</p>
      * <p>The possible values in this key correspond to the values defined in
      * android.os.Build.VERSION_CODES. Each version defines a set of feature combinations the
      * camera device must reliably report whether they are supported via
-     * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }
+     * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
      * API. And the version is always less or equal to android.os.Build.VERSION.SDK_INT.</p>
      * <p>If set to UPSIDE_DOWN_CAKE, this camera device doesn't support
-     * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }.
+     * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }.
      * Calling the method for this camera ID throws an UnsupportedOperationException.</p>
      * <p>If set to VANILLA_ICE_CREAM, the application can call
-     * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }
+     * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
      * to check if the combinations of below features are supported.</p>
      * <ul>
      * <li>A subset of LIMITED-level device stream combinations.</li>
@@ -6082,11 +6082,11 @@
 
     /**
      * <p>Minimum and maximum padding zoom factors supported by this camera device for
-     * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used for the
+     * android.efv.paddingZoomFactor used for the
      * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
      * extension.</p>
      * <p>The minimum and maximum padding zoom factors supported by the device for
-     * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used as part of the
+     * android.efv.paddingZoomFactor used as part of the
      * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
      * extension feature. This extension specific camera characteristic can be queried using
      * {@link android.hardware.camera2.CameraExtensionCharacteristics#get }.</p>
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index e24c98e..9fb561b 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -911,10 +911,10 @@
      * </ul>
      * <p>Combinations of logical and physical streams, or physical streams from different
      * physical cameras are not guaranteed. However, if the camera device supports
-     * {@link CameraManager#isSessionConfigurationWithParametersSupported },
+     * {@link CameraDevice#isSessionConfigurationSupported },
      * application must be able to query whether a stream combination involving physical
      * streams is supported by calling
-     * {@link CameraManager#isSessionConfigurationWithParametersSupported }.</p>
+     * {@link CameraDevice#isSessionConfigurationSupported }.</p>
      * <p>Camera application shouldn't assume that there are at most 1 rear camera and 1 front
      * camera in the system. For an application that switches between front and back cameras,
      * the recommendation is to switch between the first rear camera and the first front
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 9fbe348..f3b7b91 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -260,7 +260,7 @@
          * smaller sizes, then the resulting
          * {@link android.hardware.camera2.params.SessionConfiguration session configuration} can
          * be tested either by calling {@link CameraDevice#createCaptureSession} or
-         * {@link CameraManager#isSessionConfigurationWithParametersSupported}.
+         * {@link CameraDeviceSetup#isSessionConfigurationSupported}.
          *
          * @return non-modifiable ascending list of available sizes.
          */
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 1b0a485..066c45f 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -18,7 +18,6 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.MANAGE_BIOMETRIC;
-import static android.Manifest.permission.TEST_BIOMETRIC;
 import static android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION;
 import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
@@ -30,7 +29,6 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
-import android.annotation.TestApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.biometrics.BiometricAuthenticator;
@@ -38,7 +36,6 @@
 import android.hardware.biometrics.BiometricFaceConstants;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricStateListener;
-import android.hardware.biometrics.BiometricTestSession;
 import android.hardware.biometrics.CryptoObject;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.os.Binder;
@@ -72,7 +69,6 @@
  */
 @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
 @SystemApi
-@TestApi
 @SystemService(Context.FACE_SERVICE)
 public class FaceManager implements BiometricAuthenticator, BiometricFaceConstants {
 
@@ -784,8 +780,6 @@
      * @hide
      */
     @NonNull
-    @TestApi
-    @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
     public List<FaceSensorProperties> getSensorProperties() {
         final List<FaceSensorProperties> properties = new ArrayList<>();
         final List<FaceSensorPropertiesInternal> internalProperties
@@ -1634,23 +1628,4 @@
         Slog.w(TAG, "Unknown enrollment acquired message: " + acquireInfo + ", " + vendorCode);
         return null;
     }
-
-    /**
-     * Retrieves a test session for FaceManager.
-     *
-     * @hide
-     */
-    @TestApi
-    @NonNull
-    @RequiresPermission(TEST_BIOMETRIC)
-    @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
-    public BiometricTestSession createTestSession(int sensorId) {
-        try {
-            return new BiometricTestSession(mContext, sensorId,
-                    (context, sensorId1, callback) -> mService
-                            .createTestSession(sensorId1, callback, context.getOpPackageName()));
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-}
\ No newline at end of file
+}
diff --git a/core/java/android/hardware/face/FaceSensorProperties.java b/core/java/android/hardware/face/FaceSensorProperties.java
index a1ddb0e..f613127 100644
--- a/core/java/android/hardware/face/FaceSensorProperties.java
+++ b/core/java/android/hardware/face/FaceSensorProperties.java
@@ -16,12 +16,8 @@
 
 package android.hardware.face;
 
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
-
-import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.TestApi;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.SensorProperties;
 
@@ -34,8 +30,6 @@
  * Container for face sensor properties.
  * @hide
  */
-@TestApi
-@FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
 public class FaceSensorProperties extends SensorProperties {
     /**
      * @hide
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 6515312..b98c0cb 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -39,7 +39,7 @@
 interface IFaceService {
 
     // Creates a test session with the specified sensorId
-    @EnforcePermission("TEST_BIOMETRIC")
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     ITestSession createTestSession(int sensorId, ITestSessionCallback callback, String opPackageName);
 
     // Requests a proto dump of the specified sensor
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 1f54959..2816f77 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -27,6 +27,7 @@
 import android.hardware.input.IKeyboardBacklightState;
 import android.hardware.input.IStickyModifierStateListener;
 import android.hardware.input.ITabletModeChangedListener;
+import android.hardware.input.KeyboardLayoutSelectionResult;
 import android.hardware.input.TouchCalibration;
 import android.os.CombinedVibration;
 import android.hardware.input.IInputSensorEventListener;
@@ -120,8 +121,9 @@
             String keyboardLayoutDescriptor);
 
     // New Keyboard layout config APIs
-    String getKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier, int userId,
-            in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype);
+    KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
+            in InputDeviceIdentifier identifier, int userId, in InputMethodInfo imeInfo,
+            in InputMethodSubtype imeSubtype);
 
     @EnforcePermission("SET_KEYBOARD_LAYOUT")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 744dfae9..a1242fb 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -784,10 +784,10 @@
      *
      * @hide
      */
-    @Nullable
-    public String getKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
-            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @Nullable InputMethodSubtype imeSubtype) {
+    @NonNull
+    public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
+            @NonNull InputDeviceIdentifier identifier, @UserIdInt int userId,
+            @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
         try {
             return mIm.getKeyboardLayoutForInputDevice(identifier, userId, imeInfo, imeSubtype);
         } catch (RemoteException ex) {
diff --git a/core/java/android/hardware/input/KeyboardLayoutSelectionResult.aidl b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.aidl
new file mode 100644
index 0000000..13be2ff
--- /dev/null
+++ b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+parcelable KeyboardLayoutSelectionResult;
diff --git a/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java
new file mode 100644
index 0000000..5a1c947
--- /dev/null
+++ b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides information about the selected layout and the selection criteria when the caller calls
+ * {@link InputManager#getKeyboardLayoutForInputDevice(InputDeviceIdentifier, int, InputMethodInfo,
+ * InputMethodSubtype)}
+ *
+ * @hide
+ */
+
+@DataClass(genParcelable = true, genToString = true, genEqualsHashCode = true)
+public final class KeyboardLayoutSelectionResult implements Parcelable {
+    @Nullable
+    private final String mLayoutDescriptor;
+
+    /** Unspecified layout selection criteria */
+    public static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED = 0;
+
+    /** Manual selection by user */
+    public static final int LAYOUT_SELECTION_CRITERIA_USER = 1;
+
+    /** Auto-detection based on device provided language tag and layout type */
+    public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 2;
+
+    /** Auto-detection based on IME provided language tag and layout type */
+    public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 3;
+
+    /** Default selection */
+    public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 4;
+
+    /** Failed layout selection */
+    public static final KeyboardLayoutSelectionResult FAILED = new KeyboardLayoutSelectionResult(
+            null, LAYOUT_SELECTION_CRITERIA_UNSPECIFIED);
+
+    @LayoutSelectionCriteria
+    private final int mSelectionCriteria;
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @IntDef(prefix = "LAYOUT_SELECTION_CRITERIA_", value = {
+        LAYOUT_SELECTION_CRITERIA_UNSPECIFIED,
+        LAYOUT_SELECTION_CRITERIA_USER,
+        LAYOUT_SELECTION_CRITERIA_DEVICE,
+        LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+        LAYOUT_SELECTION_CRITERIA_DEFAULT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface LayoutSelectionCriteria {}
+
+    @DataClass.Generated.Member
+    public static String layoutSelectionCriteriaToString(@LayoutSelectionCriteria int value) {
+        switch (value) {
+            case LAYOUT_SELECTION_CRITERIA_UNSPECIFIED:
+                    return "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED";
+            case LAYOUT_SELECTION_CRITERIA_USER:
+                    return "LAYOUT_SELECTION_CRITERIA_USER";
+            case LAYOUT_SELECTION_CRITERIA_DEVICE:
+                    return "LAYOUT_SELECTION_CRITERIA_DEVICE";
+            case LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD:
+                    return "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD";
+            case LAYOUT_SELECTION_CRITERIA_DEFAULT:
+                    return "LAYOUT_SELECTION_CRITERIA_DEFAULT";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    @DataClass.Generated.Member
+    public KeyboardLayoutSelectionResult(
+            @Nullable String layoutDescriptor,
+            @LayoutSelectionCriteria int selectionCriteria) {
+        this.mLayoutDescriptor = layoutDescriptor;
+        this.mSelectionCriteria = selectionCriteria;
+
+        if (!(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_UNSPECIFIED)
+                && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER)
+                && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE)
+                && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD)
+                && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT)) {
+            throw new java.lang.IllegalArgumentException(
+                    "selectionCriteria was " + mSelectionCriteria + " but must be one of: "
+                            + "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED(" + LAYOUT_SELECTION_CRITERIA_UNSPECIFIED + "), "
+                            + "LAYOUT_SELECTION_CRITERIA_USER(" + LAYOUT_SELECTION_CRITERIA_USER + "), "
+                            + "LAYOUT_SELECTION_CRITERIA_DEVICE(" + LAYOUT_SELECTION_CRITERIA_DEVICE + "), "
+                            + "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD(" + LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD + "), "
+                            + "LAYOUT_SELECTION_CRITERIA_DEFAULT(" + LAYOUT_SELECTION_CRITERIA_DEFAULT + ")");
+        }
+
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public @Nullable String getLayoutDescriptor() {
+        return mLayoutDescriptor;
+    }
+
+    @DataClass.Generated.Member
+    public @LayoutSelectionCriteria int getSelectionCriteria() {
+        return mSelectionCriteria;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "KeyboardLayoutSelectionResult { " +
+                "layoutDescriptor = " + mLayoutDescriptor + ", " +
+                "selectionCriteria = " + layoutSelectionCriteriaToString(mSelectionCriteria) +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(KeyboardLayoutSelectionResult other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        KeyboardLayoutSelectionResult that = (KeyboardLayoutSelectionResult) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && java.util.Objects.equals(mLayoutDescriptor, that.mLayoutDescriptor)
+                && mSelectionCriteria == that.mSelectionCriteria;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + java.util.Objects.hashCode(mLayoutDescriptor);
+        _hash = 31 * _hash + mSelectionCriteria;
+        return _hash;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        byte flg = 0;
+        if (mLayoutDescriptor != null) flg |= 0x1;
+        dest.writeByte(flg);
+        if (mLayoutDescriptor != null) dest.writeString(mLayoutDescriptor);
+        dest.writeInt(mSelectionCriteria);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    /* package-private */ KeyboardLayoutSelectionResult(@NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        byte flg = in.readByte();
+        String layoutDescriptor = (flg & 0x1) == 0 ? null : in.readString();
+        int selectionCriteria = in.readInt();
+
+        this.mLayoutDescriptor = layoutDescriptor;
+        this.mSelectionCriteria = selectionCriteria;
+
+        if (!(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_UNSPECIFIED)
+                && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER)
+                && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE)
+                && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD)
+                && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT)) {
+            throw new java.lang.IllegalArgumentException(
+                    "selectionCriteria was " + mSelectionCriteria + " but must be one of: "
+                            + "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED(" + LAYOUT_SELECTION_CRITERIA_UNSPECIFIED + "), "
+                            + "LAYOUT_SELECTION_CRITERIA_USER(" + LAYOUT_SELECTION_CRITERIA_USER + "), "
+                            + "LAYOUT_SELECTION_CRITERIA_DEVICE(" + LAYOUT_SELECTION_CRITERIA_DEVICE + "), "
+                            + "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD(" + LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD + "), "
+                            + "LAYOUT_SELECTION_CRITERIA_DEFAULT(" + LAYOUT_SELECTION_CRITERIA_DEFAULT + ")");
+        }
+
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @NonNull Parcelable.Creator<KeyboardLayoutSelectionResult> CREATOR
+            = new Parcelable.Creator<KeyboardLayoutSelectionResult>() {
+        @Override
+        public KeyboardLayoutSelectionResult[] newArray(int size) {
+            return new KeyboardLayoutSelectionResult[size];
+        }
+
+        @Override
+        public KeyboardLayoutSelectionResult createFromParcel(@NonNull android.os.Parcel in) {
+            return new KeyboardLayoutSelectionResult(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1709568115865L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java",
+            inputSignatures = "private final @android.annotation.Nullable java.lang.String mLayoutDescriptor\npublic static final  int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED\npublic static final  int LAYOUT_SELECTION_CRITERIA_USER\npublic static final  int LAYOUT_SELECTION_CRITERIA_DEVICE\npublic static final  int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD\npublic static final  int LAYOUT_SELECTION_CRITERIA_DEFAULT\npublic static final  android.hardware.input.KeyboardLayoutSelectionResult FAILED\nprivate final @android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria int mSelectionCriteria\nclass KeyboardLayoutSelectionResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genToString=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 3149de4..beb9a93 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -120,7 +120,6 @@
     private ClassLoader mClassLoader;
     private String mLibrarySearchPaths;
     private String mLibraryPermittedPaths;
-    private GameManager mGameManager;
 
     private int mAngleOptInIndex = -1;
     private boolean mShouldUseAngle = false;
@@ -134,8 +133,6 @@
         final ApplicationInfo appInfoWithMetaData =
                 getAppInfoWithMetadata(context, pm, packageName);
 
-        mGameManager = context.getSystemService(GameManager.class);
-
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers");
         setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData);
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
@@ -161,9 +158,11 @@
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
 
         Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "notifyGraphicsEnvironmentSetup");
-        if (mGameManager != null
-                && appInfoWithMetaData.category == ApplicationInfo.CATEGORY_GAME) {
-            mGameManager.notifyGraphicsEnvironmentSetup();
+        if (appInfoWithMetaData.category == ApplicationInfo.CATEGORY_GAME) {
+            final GameManager gameManager = context.getSystemService(GameManager.class);
+            if (gameManager != null) {
+                gameManager.notifyGraphicsEnvironmentSetup();
+            }
         }
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
     }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index ccb534e..9757a10 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -4708,6 +4708,9 @@
      * Sets the user as enabled, if such an user exists.
      *
      * <p>Note that the default is true, it's only that managed profiles might not be enabled.
+     * (Managed profiles created by DevicePolicyManager will start out disabled, and DPM will later
+     * toggle them to enabled once they are provisioned. This is the primary purpose of the
+     * {@link UserInfo#FLAG_DISABLED} flag.)
      * Also ephemeral users can be disabled to indicate that their removal is in progress and they
      * shouldn't be re-entered. Therefore ephemeral users should not be re-enabled once disabled.
      *
@@ -5259,7 +5262,7 @@
 
     /**
      * Returns list of the profiles of userId including userId itself.
-     * Note that this returns only enabled.
+     * Note that this returns only {@link UserInfo#isEnabled() enabled} profiles.
      * <p>Note that this includes all profile types (not including Restricted profiles).
      *
      * <p>Requires {@link android.Manifest.permission#MANAGE_USERS} or
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index abfa4e3..d9400ac 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -1,4 +1,5 @@
 package: "android.os"
+container: "system"
 
 flag {
     name: "android_os_build_vanilla_ice_cream"
@@ -40,6 +41,7 @@
     namespace: "profile_experiences"
     description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion."
     bug: "299069460"
+    is_exported: true
 }
 
 flag {
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index fa9f03d..410f510 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1788,6 +1788,9 @@
 
     /**
      * Gets the permission states for requested package and persistent device.
+     * <p>
+     * <strong>Note: </strong>Default device permissions are not inherited in this API. Returns the
+     * exact permission states for the requested device.
      *
      * @param packageName name of the package you are checking against
      * @param persistentDeviceId id of the persistent device you are checking against
@@ -2073,5 +2076,29 @@
                 return new PermissionState[size];
             }
         };
+
+        /** @hide */
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            PermissionState that = (PermissionState) o;
+            return mGranted == that.mGranted && mFlags == that.mFlags;
+        }
+
+        /** @hide */
+        @Override
+        public int hashCode() {
+            return Objects.hash(mGranted, mFlags);
+        }
+
+        /** @hide */
+        @Override
+        public String toString() {
+            return "PermissionState{"
+                    + "mGranted=" + mGranted
+                    + ", mFlags=" + mFlags
+                    + '}';
+        }
     }
 }
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index de7008b..dc782d4 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -138,3 +138,11 @@
     bug: "325356776"
 }
 
+flag {
+    name: "runtime_permission_appops_mapping_enabled"
+    is_fixed_read_only: true
+    namespace: "permissions"
+    description: "Use runtime permission state to determine appop state"
+    bug: "266164193"
+}
+
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index c03dc71..120846c 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -667,7 +667,8 @@
             @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
             public @NonNull AddCallParametersBuilder setAssertedDisplayName(
                     String assertedDisplayName) {
-                if (assertedDisplayName.length() > MAX_NUMBER_OF_CHARACTERS) {
+                if (assertedDisplayName != null
+                        && assertedDisplayName.length() > MAX_NUMBER_OF_CHARACTERS) {
                     throw new IllegalArgumentException("assertedDisplayName exceeds the character"
                             + " limit of " + MAX_NUMBER_OF_CHARACTERS + ".");
                 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 51585af..eea6464 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11576,6 +11576,15 @@
                 "extra_low_power_warning_acknowledged";
 
         /**
+         * Whether the emergency thermal alert would be disabled
+         * (0: default) or not (1).
+         *
+         * @hide
+         */
+        public static final String EMERGENCY_THERMAL_ALERT_DISABLED =
+                "emergency_thermal_alert_disabled";
+
+        /**
          * 0 (default) Auto battery saver suggestion has not been suppressed. 1) it has been
          * suppressed.
          * @hide
diff --git a/core/java/android/service/notification/ZenAdapters.java b/core/java/android/service/notification/ZenAdapters.java
new file mode 100644
index 0000000..b249815
--- /dev/null
+++ b/core/java/android/service/notification/ZenAdapters.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+import android.annotation.NonNull;
+import android.app.Flags;
+import android.app.NotificationManager.Policy;
+
+/**
+ * Converters between different Zen representations.
+ * @hide
+ */
+public class ZenAdapters {
+
+    /** Maps {@link Policy} to {@link ZenPolicy}. */
+    @NonNull
+    public static ZenPolicy notificationPolicyToZenPolicy(@NonNull Policy policy) {
+        ZenPolicy.Builder zenPolicyBuilder = new ZenPolicy.Builder()
+                .allowAlarms(policy.allowAlarms())
+                .allowCalls(
+                        policy.allowCalls()
+                                ? notificationPolicySendersToZenPolicyPeopleType(
+                                        policy.allowCallsFrom())
+                        : ZenPolicy.PEOPLE_TYPE_NONE)
+                .allowConversations(
+                        policy.allowConversations()
+                                ? notificationPolicyConversationSendersToZenPolicy(
+                                        policy.allowConversationsFrom())
+                                : ZenPolicy.CONVERSATION_SENDERS_NONE)
+                .allowEvents(policy.allowEvents())
+                .allowMedia(policy.allowMedia())
+                .allowMessages(
+                        policy.allowMessages()
+                                ? notificationPolicySendersToZenPolicyPeopleType(
+                                        policy.allowMessagesFrom())
+                                : ZenPolicy.PEOPLE_TYPE_NONE)
+                .allowReminders(policy.allowReminders())
+                .allowRepeatCallers(policy.allowRepeatCallers())
+                .allowSystem(policy.allowSystem());
+
+        if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
+            zenPolicyBuilder.showBadges(policy.showBadges())
+                    .showFullScreenIntent(policy.showFullScreenIntents())
+                    .showInAmbientDisplay(policy.showAmbient())
+                    .showInNotificationList(policy.showInNotificationList())
+                    .showLights(policy.showLights())
+                    .showPeeking(policy.showPeeking())
+                    .showStatusBarIcons(policy.showStatusBarIcons());
+        }
+
+        if (Flags.modesApi()) {
+            zenPolicyBuilder.allowPriorityChannels(policy.allowPriorityChannels());
+        }
+
+        return zenPolicyBuilder.build();
+    }
+
+    /** Maps {@link ZenPolicy.PeopleType} enum to {@link Policy.PrioritySenders}. */
+    @Policy.PrioritySenders
+    public static int zenPolicyPeopleTypeToNotificationPolicySenders(
+            @ZenPolicy.PeopleType int zpPeopleType, @Policy.PrioritySenders int defaultResult) {
+        switch (zpPeopleType) {
+            case ZenPolicy.PEOPLE_TYPE_ANYONE:
+                return Policy.PRIORITY_SENDERS_ANY;
+            case ZenPolicy.PEOPLE_TYPE_CONTACTS:
+                return Policy.PRIORITY_SENDERS_CONTACTS;
+            case ZenPolicy.PEOPLE_TYPE_STARRED:
+                return Policy.PRIORITY_SENDERS_STARRED;
+            default:
+                return defaultResult;
+        }
+    }
+
+    /** Maps {@link Policy.PrioritySenders} enum to {@link ZenPolicy.PeopleType}. */
+    @ZenPolicy.PeopleType
+    public static int notificationPolicySendersToZenPolicyPeopleType(
+            @Policy.PrioritySenders int npPrioritySenders) {
+        switch (npPrioritySenders) {
+            case Policy.PRIORITY_SENDERS_ANY:
+                return ZenPolicy.PEOPLE_TYPE_ANYONE;
+            case Policy.PRIORITY_SENDERS_CONTACTS:
+                return ZenPolicy.PEOPLE_TYPE_CONTACTS;
+            case Policy.PRIORITY_SENDERS_STARRED:
+            default:
+                return ZenPolicy.PEOPLE_TYPE_STARRED;
+        }
+    }
+
+    /** Maps {@link ZenPolicy.ConversationSenders} enum to {@link Policy.ConversationSenders}. */
+    @Policy.ConversationSenders
+    public static int zenPolicyConversationSendersToNotificationPolicy(
+            @ZenPolicy.ConversationSenders int zpConversationSenders,
+            @Policy.ConversationSenders int defaultResult) {
+        switch (zpConversationSenders) {
+            case ZenPolicy.CONVERSATION_SENDERS_ANYONE:
+                return Policy.CONVERSATION_SENDERS_ANYONE;
+            case ZenPolicy.CONVERSATION_SENDERS_IMPORTANT:
+                return Policy.CONVERSATION_SENDERS_IMPORTANT;
+            case ZenPolicy.CONVERSATION_SENDERS_NONE:
+                return Policy.CONVERSATION_SENDERS_NONE;
+            default:
+                return defaultResult;
+        }
+    }
+
+    /** Maps {@link Policy.ConversationSenders} enum to {@link ZenPolicy.ConversationSenders}. */
+    @ZenPolicy.ConversationSenders
+    private static int notificationPolicyConversationSendersToZenPolicy(
+            @Policy.ConversationSenders int npPriorityConversationSenders) {
+        switch (npPriorityConversationSenders) {
+            case Policy.CONVERSATION_SENDERS_ANYONE:
+                return ZenPolicy.CONVERSATION_SENDERS_ANYONE;
+            case Policy.CONVERSATION_SENDERS_IMPORTANT:
+                return ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+            case Policy.CONVERSATION_SENDERS_NONE:
+                return ZenPolicy.CONVERSATION_SENDERS_NONE;
+            default: // including Policy.CONVERSATION_SENDERS_UNSET
+                return ZenPolicy.CONVERSATION_SENDERS_UNSET;
+        }
+    }
+}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index d9ca935..1d6dd1e 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -24,6 +24,9 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
+import static android.service.notification.ZenAdapters.notificationPolicySendersToZenPolicyPeopleType;
+import static android.service.notification.ZenAdapters.zenPolicyConversationSendersToNotificationPolicy;
+import static android.service.notification.ZenAdapters.zenPolicyPeopleTypeToNotificationPolicySenders;
 
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
@@ -1269,11 +1272,11 @@
     public ZenPolicy toZenPolicy() {
         ZenPolicy.Builder builder = new ZenPolicy.Builder()
                 .allowCalls(allowCalls
-                        ? ZenModeConfig.getZenPolicySenders(allowCallsFrom)
+                        ? notificationPolicySendersToZenPolicyPeopleType(allowCallsFrom)
                         : ZenPolicy.PEOPLE_TYPE_NONE)
                 .allowRepeatCallers(allowRepeatCallers)
                 .allowMessages(allowMessages
-                        ? ZenModeConfig.getZenPolicySenders(allowMessagesFrom)
+                        ? notificationPolicySendersToZenPolicyPeopleType(allowMessagesFrom)
                         : ZenPolicy.PEOPLE_TYPE_NONE)
                 .allowReminders(allowReminders)
                 .allowEvents(allowEvents)
@@ -1333,14 +1336,14 @@
         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_MESSAGES,
                 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_MESSAGES, defaultPolicy))) {
             priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES;
-            messageSenders = getNotificationPolicySenders(zenPolicy.getPriorityMessageSenders(),
-                    messageSenders);
+            messageSenders = zenPolicyPeopleTypeToNotificationPolicySenders(
+                    zenPolicy.getPriorityMessageSenders(), messageSenders);
         }
 
         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CONVERSATIONS,
                 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CONVERSATIONS, defaultPolicy))) {
             priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS;
-            conversationSenders = getConversationSendersWithDefault(
+            conversationSenders = zenPolicyConversationSendersToNotificationPolicy(
                     zenPolicy.getPriorityConversationSenders(), conversationSenders);
         } else {
             conversationSenders = CONVERSATION_SENDERS_NONE;
@@ -1349,8 +1352,8 @@
         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CALLS,
                 isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CALLS, defaultPolicy))) {
             priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS;
-            callSenders = getNotificationPolicySenders(zenPolicy.getPriorityCallSenders(),
-                    callSenders);
+            callSenders = zenPolicyPeopleTypeToNotificationPolicySenders(
+                    zenPolicy.getPriorityCallSenders(), callSenders);
         }
 
         if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS,
@@ -1449,47 +1452,6 @@
         return (policy.suppressedVisualEffects & visualEffect) == 0;
     }
 
-    private static int getNotificationPolicySenders(@ZenPolicy.PeopleType int senders,
-            int defaultPolicySender) {
-        switch (senders) {
-            case ZenPolicy.PEOPLE_TYPE_ANYONE:
-                return Policy.PRIORITY_SENDERS_ANY;
-            case ZenPolicy.PEOPLE_TYPE_CONTACTS:
-                return Policy.PRIORITY_SENDERS_CONTACTS;
-            case ZenPolicy.PEOPLE_TYPE_STARRED:
-                return Policy.PRIORITY_SENDERS_STARRED;
-            default:
-                return defaultPolicySender;
-        }
-    }
-
-    private static int getConversationSendersWithDefault(@ZenPolicy.ConversationSenders int senders,
-            int defaultPolicySender) {
-        switch (senders) {
-            case ZenPolicy.CONVERSATION_SENDERS_ANYONE:
-            case ZenPolicy.CONVERSATION_SENDERS_IMPORTANT:
-            case ZenPolicy.CONVERSATION_SENDERS_NONE:
-                return senders;
-            default:
-                return defaultPolicySender;
-        }
-    }
-
-    /**
-     * Maps NotificationManager.Policy senders type to ZenPolicy.PeopleType
-     */
-    public static @ZenPolicy.PeopleType int getZenPolicySenders(int senders) {
-        switch (senders) {
-            case Policy.PRIORITY_SENDERS_ANY:
-                return ZenPolicy.PEOPLE_TYPE_ANYONE;
-            case Policy.PRIORITY_SENDERS_CONTACTS:
-                return ZenPolicy.PEOPLE_TYPE_CONTACTS;
-            case Policy.PRIORITY_SENDERS_STARRED:
-            default:
-                return ZenPolicy.PEOPLE_TYPE_STARRED;
-        }
-    }
-
     public Policy toNotificationPolicy() {
         int priorityCategories = 0;
         int priorityCallSenders = Policy.PRIORITY_SENDERS_CONTACTS;
@@ -1524,7 +1486,7 @@
         }
         priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
         priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
-        priorityConversationSenders = getConversationSendersWithDefault(
+        priorityConversationSenders = zenPolicyConversationSendersToNotificationPolicy(
                 allowConversationsFrom, priorityConversationSenders);
 
         int state = areChannelsBypassingDnd ? Policy.STATE_CHANNELS_BYPASSING_DND : 0;
@@ -1559,15 +1521,6 @@
         }
     }
 
-    private static int prioritySendersToSource(int prioritySenders, int def) {
-        switch (prioritySenders) {
-            case Policy.PRIORITY_SENDERS_CONTACTS: return SOURCE_CONTACT;
-            case Policy.PRIORITY_SENDERS_STARRED: return SOURCE_STAR;
-            case Policy.PRIORITY_SENDERS_ANY: return SOURCE_ANYONE;
-            default: return def;
-        }
-    }
-
     private static int normalizePrioritySenders(int prioritySenders, int def) {
         if (!(prioritySenders == Policy.PRIORITY_SENDERS_CONTACTS
                 || prioritySenders == Policy.PRIORITY_SENDERS_STARRED
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 20adc54..306410c 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -519,7 +519,7 @@
             @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AlwaysOnHotwordDetector.Callback callback) {
-        // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
+        // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
 
         Objects.requireNonNull(keyphrase);
         Objects.requireNonNull(locale);
@@ -545,10 +545,6 @@
             @NonNull SoundTrigger.ModuleProperties moduleProperties,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AlwaysOnHotwordDetector.Callback callback) {
-        // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
-        // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale,
-        // SoundTrigger.ModuleProperties, AlwaysOnHotwordDetector.Callback)} and replace with the
-        // permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
 
         Objects.requireNonNull(keyphrase);
         Objects.requireNonNull(locale);
@@ -615,11 +611,6 @@
             @Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory,
             @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
-        // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
-        // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
-        // AlwaysOnHotwordDetector.Callback)} and replace with the permission
-        // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
-
         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
                 /* supportHotwordDetectionService= */ true, options, sharedMemory,
                 /* modulProperties */ null, /* executor= */ null, callback);
@@ -671,11 +662,7 @@
             @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AlwaysOnHotwordDetector.Callback callback) {
-        // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
-        // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
-        // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
-        // Executor, AlwaysOnHotwordDetector.Callback)} and replace with the permission
-        // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
+        // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
 
         Objects.requireNonNull(keyphrase);
         Objects.requireNonNull(locale);
@@ -702,10 +689,6 @@
             @NonNull SoundTrigger.ModuleProperties moduleProperties,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AlwaysOnHotwordDetector.Callback callback) {
-        // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
-        // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale, PersistableBundle,
-        // SharedMemory, SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)}
-        // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
 
         Objects.requireNonNull(keyphrase);
         Objects.requireNonNull(locale);
diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java
index 8a44d4f..798203f 100644
--- a/core/java/android/view/ScrollFeedbackProvider.java
+++ b/core/java/android/view/ScrollFeedbackProvider.java
@@ -21,8 +21,11 @@
 import android.view.flags.Flags;
 
 /**
- * Interface to represent an entity giving consistent feedback for different events surrounding view
- * scroll.
+ * Provides feedback to the user for scroll events on a {@link View}. The type of feedback provided
+ * to the user may depend on the {@link InputDevice} that generated the scroll events.
+ *
+ * <p>An example of the type of feedback that this interface may provide is haptic feedback (that
+ * is, tactile feedback that provide the user physical feedback for their scroll).
  *
  * <p>The interface provides methods for the client to report different scroll events. The client
  * should report all scroll events that they want to be considered for scroll feedback using the
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 7f79661..d992feb 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -182,7 +182,6 @@
             PHASE_CLIENT_ANIMATION_FINISHED_SHOW,
             PHASE_CLIENT_ANIMATION_FINISHED_HIDE,
             PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT,
-            PHASE_CLIENT_ANIMATION_FINISHED_HIDE,
             PHASE_IME_SHOW_WINDOW,
             PHASE_IME_HIDE_WINDOW,
             PHASE_IME_PRIVILEGED_OPERATIONS,
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index 3fc0a30..8501474 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -175,8 +175,16 @@
 
     /**
      * Adds the WebView asset path to {@link android.content.res.AssetManager}.
+     * If {@link android.content.res.Flags#FLAG_REGISTER_RESOURCE_PATHS} is enabled, this function
+     * will be a no-op because the asset paths appending work will only be handled by
+     * {@link android.content.res.Resources#registerResourcePaths(String, ApplicationInfo)},
+     * otherwise it behaves the old way.
      */
     public void addWebViewAssetPath(Context context) {
+        if (android.content.res.Flags.registerResourcePaths()) {
+            return;
+        }
+
         final String[] newAssetPaths =
                 WebViewFactory.getLoadedPackageInfo().applicationInfo.getAllApkPaths();
         final ApplicationInfo appInfo = context.getApplicationInfo();
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index c748a57..01fdd1d 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -30,6 +30,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
+import android.content.res.Resources;
 import android.os.Build;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -544,8 +545,14 @@
             Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
             try {
                 sTimestamps.mAddAssetsStart = SystemClock.uptimeMillis();
-                for (String newAssetPath : webViewContext.getApplicationInfo().getAllApkPaths()) {
-                    initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
+                if (android.content.res.Flags.registerResourcePaths()) {
+                    Resources.registerResourcePaths(webViewContext.getPackageName(),
+                            webViewContext.getApplicationInfo());
+                } else {
+                    for (String newAssetPath : webViewContext.getApplicationInfo()
+                            .getAllApkPaths()) {
+                        initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
+                    }
                 }
                 sTimestamps.mAddAssetsEnd = sTimestamps.mGetClassLoaderStart =
                         SystemClock.uptimeMillis();
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0e5747d..fe4ac4e 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1105,6 +1105,7 @@
         SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
             mViewId = parcel.readInt();
             mIntentId = parcel.readInt();
+            mIsReplacedIntoAction = parcel.readBoolean();
             mServiceIntent = parcel.readTypedObject(Intent.CREATOR);
             mItems = mServiceIntent != null
                     ? null
@@ -1128,6 +1129,7 @@
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(mViewId);
             dest.writeInt(mIntentId);
+            dest.writeBoolean(mIsReplacedIntoAction);
             dest.writeTypedObject(mServiceIntent, flags);
             if (mItems != null) {
                 mItems.writeToParcel(dest, flags, /* attached= */ true);
@@ -1209,6 +1211,19 @@
     }
 
     /**
+     * The maximum size for RemoteViews with converted RemoteCollectionItemsAdapter.
+     * When converting RemoteViewsAdapter to RemoteCollectionItemsAdapter, we want to put size
+     * limits on each unique RemoteCollectionItems in order to not exceed the transaction size limit
+     * for each parcel (typically 1 MB). We leave a certain ratio of the maximum size as a buffer
+     * for missing calculations of certain parameters (e.g. writing a RemoteCollectionItems to the
+     * parcel will write its Id array as well, but that is missing when writing itschild RemoteViews
+     * directly to the parcel as we did in RemoteViewsService)
+     *
+     * @hide
+     */
+    private static final int MAX_SINGLE_PARCEL_SIZE = (int) (1_000_000 * 0.8);
+
+    /**
      * @hide
      */
     public CompletableFuture<Void> collectAllIntents() {
@@ -1260,17 +1275,47 @@
             return mUriToCollectionMapping.get(uri);
         }
 
-        CompletableFuture<Void> collectAllIntentsNoComplete(@NonNull RemoteViews inViews) {
-            CompletableFuture<Void> collectionFuture = CompletableFuture.completedFuture(null);
+        public @NonNull CompletableFuture<Void> collectAllIntentsNoComplete(
+                @NonNull RemoteViews inViews) {
+            SparseArray<Intent> idToIntentMapping = new SparseArray<>();
+            // Collect the number of uinque Intent (which is equal to the number of new connections
+            // to make) for size allocation and exclude certain collections from being written to
+            // the parcel to better estimate the space left for reallocation.
+            collectAllIntentsInternal(inViews, idToIntentMapping);
+
+            // Calculate the individual size here
+            int numOfIntents = idToIntentMapping.size();
+            if (numOfIntents == 0) {
+                Log.e(LOG_TAG, "Possibly notifying updates for nonexistent view Id");
+                return CompletableFuture.completedFuture(null);
+            }
+
+            Parcel sizeTestParcel = Parcel.obtain();
+            // Write self RemoteViews to the parcel, which includes the actions/bitmaps/collection
+            // cache to see how much space is left for the RemoteCollectionItems that are to be
+            // updated.
+            RemoteViews.this.writeToParcel(sizeTestParcel,
+                    /* flags= */ 0,
+                    /* intentsToIgnore= */ idToIntentMapping);
+            int remainingSize = MAX_SINGLE_PARCEL_SIZE - sizeTestParcel.dataSize();
+            sizeTestParcel.recycle();
+
+            int individualSize = remainingSize < 0
+                    ? 0
+                    : remainingSize / numOfIntents;
+
+            return connectAllUniqueIntents(individualSize, idToIntentMapping);
+        }
+
+        private void collectAllIntentsInternal(@NonNull RemoteViews inViews,
+                @NonNull SparseArray<Intent> idToIntentMapping) {
             if (inViews.hasSizedRemoteViews()) {
                 for (RemoteViews remoteViews : inViews.mSizedRemoteViews) {
-                    collectionFuture = CompletableFuture.allOf(collectionFuture,
-                            collectAllIntentsNoComplete(remoteViews));
+                    collectAllIntentsInternal(remoteViews, idToIntentMapping);
                 }
             } else if (inViews.hasLandscapeAndPortraitLayouts()) {
-                collectionFuture = CompletableFuture.allOf(
-                        collectAllIntentsNoComplete(inViews.mLandscape),
-                        collectAllIntentsNoComplete(inViews.mPortrait));
+                collectAllIntentsInternal(inViews.mLandscape, idToIntentMapping);
+                collectAllIntentsInternal(inViews.mPortrait, idToIntentMapping);
             } else if (inViews.mActions != null) {
                 for (Action action : inViews.mActions) {
                     if (action instanceof SetRemoteCollectionItemListAdapterAction rca) {
@@ -1280,13 +1325,16 @@
                         }
 
                         if (rca.mIntentId != -1 && rca.mIsReplacedIntoAction) {
-                            final String uri = mIdToUriMapping.get(rca.mIntentId);
-                            collectionFuture = CompletableFuture.allOf(collectionFuture,
-                                    getItemsFutureFromIntentWithTimeout(rca.mServiceIntent)
-                                            .thenAccept(rc -> {
-                                                rc.setHierarchyRootData(getHierarchyRootData());
-                                                mUriToCollectionMapping.put(uri, rc);
-                                            }));
+                            rca.mIsReplacedIntoAction = false;
+
+                            // Avoid redundant connections for the same intent. Also making sure
+                            // that the number of connections we are making is always equal to the
+                            // nmuber of unique intents that are being used for the updates.
+                            if (idToIntentMapping.contains(rca.mIntentId)) {
+                                continue;
+                            }
+
+                            idToIntentMapping.put(rca.mIntentId, rca.mServiceIntent);
                             rca.mItems = null;
                             continue;
                         }
@@ -1295,7 +1343,7 @@
                         // intents.
                         if (rca.mServiceIntent != null) {
                             final String uri = rca.mServiceIntent.toUri(0);
-                            int index = mIdToUriMapping.indexOfValue(uri);
+                            int index = mIdToUriMapping.indexOfValueByValue(uri);
                             if (index == -1) {
                                 int newIntentId = mIdToUriMapping.size();
                                 rca.mIntentId = newIntentId;
@@ -1305,41 +1353,50 @@
                                 rca.mItems = null;
                                 continue;
                             }
-                            collectionFuture = CompletableFuture.allOf(collectionFuture,
-                                    getItemsFutureFromIntentWithTimeout(rca.mServiceIntent)
-                                            .thenAccept(rc -> {
-                                                rc.setHierarchyRootData(getHierarchyRootData());
-                                                mUriToCollectionMapping.put(uri, rc);
-                                            }));
+
+                            idToIntentMapping.put(rca.mIntentId, rca.mServiceIntent);
                             rca.mItems = null;
                         } else {
                             for (RemoteViews views : rca.mItems.mViews) {
-                                collectionFuture = CompletableFuture.allOf(collectionFuture,
-                                        collectAllIntentsNoComplete(views));
+                                collectAllIntentsInternal(views, idToIntentMapping);
                             }
                         }
                     } else if (action instanceof ViewGroupActionAdd vgaa
                             && vgaa.mNestedViews != null) {
-                        collectionFuture = CompletableFuture.allOf(collectionFuture,
-                                collectAllIntentsNoComplete(vgaa.mNestedViews));
+                        collectAllIntentsInternal(vgaa.mNestedViews, idToIntentMapping);
                     }
                 }
             }
+        }
 
-            return collectionFuture;
+        private @NonNull CompletableFuture<Void> connectAllUniqueIntents(int individualSize,
+                @NonNull SparseArray<Intent> idToIntentMapping) {
+            List<CompletableFuture<Void>> intentFutureList = new ArrayList<>();
+            for (int i = 0; i < idToIntentMapping.size(); i++) {
+                String currentIntentUri = mIdToUriMapping.get(idToIntentMapping.keyAt(i));
+                Intent currentIntent = idToIntentMapping.valueAt(i);
+                intentFutureList.add(getItemsFutureFromIntentWithTimeout(currentIntent,
+                        individualSize)
+                        .thenAccept(items -> {
+                            items.setHierarchyRootData(getHierarchyRootData());
+                            mUriToCollectionMapping.put(currentIntentUri, items);
+                        }));
+            }
+
+            return CompletableFuture.allOf(intentFutureList.toArray(CompletableFuture[]::new));
         }
 
         private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout(
-                Intent intent) {
+                Intent intent, int individualSize) {
             if (intent == null) {
                 Log.e(LOG_TAG, "Null intent received when generating adapter future");
                 return CompletableFuture.completedFuture(new RemoteCollectionItems
-                    .Builder().build());
+                        .Builder().build());
             }
 
             final Context context = ActivityThread.currentApplication();
-            final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>();
 
+            final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>();
             context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
                     result.defaultExecutor(), new ServiceConnection() {
                         @Override
@@ -1348,11 +1405,11 @@
                             RemoteCollectionItems items;
                             try {
                                 items = IRemoteViewsFactory.Stub.asInterface(iBinder)
-                                    .getRemoteCollectionItems();
+                                        .getRemoteCollectionItems(individualSize);
                             } catch (RemoteException re) {
                                 items = new RemoteCollectionItems.Builder().build();
-                                Log.e(LOG_TAG, "Error getting collection items from the factory",
-                                        re);
+                                Log.e(LOG_TAG, "Error getting collection items from the"
+                                        + " factory", re);
                             } finally {
                                 context.unbindService(this);
                             }
@@ -1371,10 +1428,17 @@
             return result;
         }
 
-        public void writeToParcel(Parcel out, int flags) {
+        public void writeToParcel(Parcel out, int flags,
+                @Nullable SparseArray<Intent> intentsToIgnore) {
             out.writeInt(mIdToUriMapping.size());
             for (int i = 0; i < mIdToUriMapping.size(); i++) {
-                out.writeInt(mIdToUriMapping.keyAt(i));
+                int currentIntentId = mIdToUriMapping.keyAt(i);
+                if (intentsToIgnore != null && intentsToIgnore.contains(currentIntentId)) {
+                    // Skip writing collections that are to be updated in the following steps to
+                    // better estimate the RemoteViews size.
+                    continue;
+                }
+                out.writeInt(currentIntentId);
                 String intentUri = mIdToUriMapping.valueAt(i);
                 out.writeString8(intentUri);
                 mUriToCollectionMapping.get(intentUri).writeToParcel(out, flags, true);
@@ -6724,7 +6788,13 @@
         return 0;
     }
 
+    @Override
     public void writeToParcel(Parcel dest, int flags) {
+        writeToParcel(dest, flags, /* intentsToIgnore= */ null);
+    }
+
+    private void writeToParcel(Parcel dest, int flags,
+            @Nullable SparseArray<Intent> intentsToIgnore) {
         boolean prevSquashingAllowed = dest.allowSquashing();
 
         if (!hasMultipleLayouts()) {
@@ -6733,7 +6803,7 @@
             // is shared by all children.
             if (mIsRoot) {
                 mBitmapCache.writeBitmapsToParcel(dest, flags);
-                mCollectionCache.writeToParcel(dest, flags);
+                mCollectionCache.writeToParcel(dest, flags, intentsToIgnore);
             }
             mApplication.writeToParcel(dest, flags);
             if (mIsRoot || mIdealSize == null) {
@@ -6750,7 +6820,7 @@
             dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS);
             if (mIsRoot) {
                 mBitmapCache.writeBitmapsToParcel(dest, flags);
-                mCollectionCache.writeToParcel(dest, flags);
+                mCollectionCache.writeToParcel(dest, flags, intentsToIgnore);
             }
             dest.writeInt(mSizedRemoteViews.size());
             for (RemoteViews view : mSizedRemoteViews) {
@@ -6762,7 +6832,7 @@
             // is shared by all children.
             if (mIsRoot) {
                 mBitmapCache.writeBitmapsToParcel(dest, flags);
-                mCollectionCache.writeToParcel(dest, flags);
+                mCollectionCache.writeToParcel(dest, flags, intentsToIgnore);
             }
             mLandscape.writeToParcel(dest, flags);
             // Both RemoteViews already share the same package and user
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index a250a86..d4a5bbd 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -19,6 +19,7 @@
 import android.app.Service;
 import android.content.Intent;
 import android.os.IBinder;
+import android.os.Parcel;
 
 import com.android.internal.widget.IRemoteViewsFactory;
 
@@ -43,13 +44,6 @@
     private static final Object sLock = new Object();
 
     /**
-     * Used for determining the maximum number of entries to retrieve from RemoteViewsFactory
-     *
-     * @hide
-     */
-    private static final int MAX_NUM_ENTRY = 10;
-
-    /**
      * An interface for an adapter between a remote collection view (ListView, GridView, etc) and
      * the underlying data for that view.  The implementor is responsible for making a RemoteView
      * for each item in the data set. This interface is a thin wrapper around {@link Adapter}.
@@ -235,9 +229,10 @@
         }
 
         @Override
-        public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+        public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) {
             RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems
                     .Builder().build();
+            Parcel capSizeTestParcel = Parcel.obtain();
 
             try {
                 RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
@@ -245,15 +240,25 @@
                 mFactory.onDataSetChanged();
 
                 itemsBuilder.setHasStableIds(mFactory.hasStableIds());
-                final int numOfEntries = Math.min(mFactory.getCount(), MAX_NUM_ENTRY);
+                final int numOfEntries = mFactory.getCount();
+
                 for (int i = 0; i < numOfEntries; i++) {
-                    itemsBuilder.addItem(mFactory.getItemId(i), mFactory.getViewAt(i));
+                    final long currentItemId = mFactory.getItemId(i);
+                    final RemoteViews currentView = mFactory.getViewAt(i);
+                    currentView.writeToParcel(capSizeTestParcel, 0);
+                    if (capSizeTestParcel.dataSize() > capSize) {
+                        break;
+                    }
+                    itemsBuilder.addItem(currentItemId, currentView);
                 }
 
                 items = itemsBuilder.build();
             } catch (Exception ex) {
                 Thread t = Thread.currentThread();
                 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+            } finally {
+                // Recycle the parcel
+                capSizeTestParcel.recycle();
             }
             return items;
         }
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 65b5979..c769518 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -172,6 +172,20 @@
         newIntent.prepareToLeaveUser(callingUserId);
         final CompletableFuture<ResolveInfo> targetResolveInfoFuture =
                 mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId);
+
+        if (isPrivateProfile(callingUserId)) {
+            buildAndExecuteForPrivateProfile(intentReceived, className, newIntent, callingUserId,
+                    targetUserId);
+        } else {
+            buildAndExecute(targetResolveInfoFuture, intentReceived, className, newIntent,
+                    callingUserId,
+                    targetUserId, userMessage, managedProfile);
+        }
+    }
+
+    private void buildAndExecute(CompletableFuture<ResolveInfo> targetResolveInfoFuture,
+            Intent intentReceived, String className, Intent newIntent, int callingUserId,
+            int targetUserId, String userMessage, UserInfo managedProfile) {
         targetResolveInfoFuture
                 .thenApplyAsync(targetResolveInfo -> {
                     if (isResolverActivityResolveInfo(targetResolveInfo)) {
@@ -195,6 +209,23 @@
                 }, getApplicationContext().getMainExecutor());
     }
 
+    private void buildAndExecuteForPrivateProfile(
+            Intent intentReceived, String className, Intent newIntent, int callingUserId,
+            int targetUserId) {
+        final CompletableFuture<ResolveInfo> targetResolveInfoFuture =
+                mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId);
+        targetResolveInfoFuture
+                .thenAcceptAsync(targetResolveInfo -> {
+                    if (isResolverActivityResolveInfo(targetResolveInfo)) {
+                        launchResolverActivityWithCorrectTab(intentReceived, className, newIntent,
+                                callingUserId, targetUserId);
+                    } else {
+                        maybeShowUserConsentMiniResolverPrivate(targetResolveInfo, newIntent,
+                                targetUserId);
+                    }
+                }, getApplicationContext().getMainExecutor());
+    }
+
     private void maybeShowUserConsentMiniResolver(
             ResolveInfo target, Intent launchIntent, UserInfo managedProfile) {
         if (target == null || isIntentForwarderResolveInfo(target) || !isDeviceProvisioned()) {
@@ -233,24 +264,70 @@
                 "Showing user consent for redirection into the managed profile for intent [%s] and "
                         + " calling package [%s]",
                 launchIntent, callingPackage));
+        PackageManager packageManagerForTargetUser =
+                createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
+                        .getPackageManager();
+        buildMiniResolver(target, launchIntent, targetUserId,
+                getOpenInWorkMessage(launchIntent, target.loadLabel(packageManagerForTargetUser)),
+                packageManagerForTargetUser);
+
+
+        View telephonyInfo = findViewById(R.id.miniresolver_info_section);
+
+        // Additional information section is work telephony specific. Therefore, it is only shown
+        // for telephony related intents, when all sim subscriptions are in the work profile.
+        if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent))
+                && devicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType()
+                == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
+            telephonyInfo.setVisibility(View.VISIBLE);
+            ((TextView) findViewById(R.id.miniresolver_info_section_text))
+                    .setText(getWorkTelephonyInfoSectionMessage(launchIntent));
+        } else {
+            telephonyInfo.setVisibility(View.GONE);
+        }
+    }
+
+    private void maybeShowUserConsentMiniResolverPrivate(
+            ResolveInfo target, Intent launchIntent, int targetUserId) {
+        if (target == null || isIntentForwarderResolveInfo(target)) {
+            finish();
+            return;
+        }
+
+        String callingPackage = getCallingPackage();
+
+        Log.i("IntentForwarderActivity", String.format(
+                "Showing user consent for redirection into the main profile for intent [%s] and "
+                        + " calling package [%s]",
+                launchIntent, callingPackage));
+        PackageManager packageManagerForTargetUser =
+                createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
+                        .getPackageManager();
+        buildMiniResolver(target, launchIntent, targetUserId,
+                getString(R.string.miniresolver_open_in_personal,
+                        target.loadLabel(packageManagerForTargetUser)),
+                packageManagerForTargetUser);
+
+        View telephonyInfo = findViewById(R.id.miniresolver_info_section);
+        telephonyInfo.setVisibility(View.GONE);
+    }
+
+    private void buildMiniResolver(ResolveInfo target, Intent launchIntent, int targetUserId,
+            String resolverTitle, PackageManager pmForTargetUser) {
         int layoutId = R.layout.miniresolver;
         setContentView(layoutId);
 
         findViewById(R.id.title_container).setElevation(0);
 
-        PackageManager packageManagerForTargetUser =
-                createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
-                        .getPackageManager();
-
         ImageView icon = findViewById(R.id.icon);
         icon.setImageDrawable(
-                getAppIcon(target, launchIntent, targetUserId, packageManagerForTargetUser));
+                getAppIcon(target, launchIntent, targetUserId, pmForTargetUser));
 
         View buttonContainer = findViewById(R.id.button_bar_container);
         buttonContainer.setPadding(0, 0, 0, buttonContainer.getPaddingBottom());
 
         ((TextView) findViewById(R.id.open_cross_profile)).setText(
-                getOpenInWorkMessage(launchIntent, target.loadLabel(packageManagerForTargetUser)));
+                resolverTitle);
 
         // The mini-resolver's negative button is reused in this flow to cancel the intent
         ((Button) findViewById(R.id.use_same_profile_browser)).setText(R.string.cancel);
@@ -269,21 +346,6 @@
                     targetUserId);
             finish();
         });
-
-
-        View telephonyInfo = findViewById(R.id.miniresolver_info_section);
-
-        // Additional information section is work telephony specific. Therefore, it is only shown
-        // for telephony related intents, when all sim subscriptions are in the work profile.
-        if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent))
-                && devicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType()
-                    == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
-            telephonyInfo.setVisibility(View.VISIBLE);
-            ((TextView) findViewById(R.id.miniresolver_info_section_text))
-                    .setText(getWorkTelephonyInfoSectionMessage(launchIntent));
-        } else {
-            telephonyInfo.setVisibility(View.GONE);
-        }
     }
 
     private Drawable getAppIcon(
@@ -548,6 +610,18 @@
     }
 
     /**
+     * Returns the private profile for this device or null if there is no private profile.
+     */
+    @Nullable
+    private UserInfo getPrivateProfile() {
+        List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId());
+        for (UserInfo userInfo : relatedUsers) {
+            if (userInfo.isPrivateProfile()) return userInfo;
+        }
+        return null;
+    }
+
+    /**
      * Returns the userId of the profile parent or UserHandle.USER_NULL if there is
      * no parent.
      */
@@ -577,6 +651,17 @@
         return mMetricsLogger;
     }
 
+    private boolean isPrivateProfile(int userId) {
+        UserInfo privateProfile = getPrivateProfile();
+        return privateSpaceFlagsEnabled() && privateProfile != null
+                && privateProfile.id == userId;
+    }
+
+    private boolean privateSpaceFlagsEnabled() {
+        return android.os.Flags.allowPrivateProfile()
+                && android.multiuser.Flags.enablePrivateSpaceIntentRedirection();
+    }
+
     @VisibleForTesting
     protected Injector createInjector() {
         return new InjectorImpl();
diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
index 918d9c0..1d0e972 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
+++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
@@ -39,6 +39,6 @@
     boolean hasStableIds();
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     boolean isCreated();
-    RemoteViews.RemoteCollectionItems getRemoteCollectionItems();
+    RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize);
 }
 
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index d5f17da..071f933 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -196,14 +196,6 @@
     return result;
 }
 
-static vector<string> parseCsv(JNIEnv* env, jstring csvJString) {
-    const char* charArray = env->GetStringUTFChars(csvJString, 0);
-    string csvString(charArray);
-    vector<string> result = parseCsv(csvString);
-    env->ReleaseStringUTFChars(csvJString, charArray);
-    return result;
-}
-
 void LayoutlibLogger(base::LogId, base::LogSeverity severity, const char* tag, const char* file,
                      unsigned int line, const char* message) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -395,20 +387,28 @@
     jmethodID getPropertyMethod = GetStaticMethodIDOrDie(env, system, "getProperty",
                                                          "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
 
-    // Get the names of classes that need to register their native methods
-    auto nativesClassesJString =
-            (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
-                                                 env->NewStringUTF("core_native_classes"),
-                                                 env->NewStringUTF(""));
-    vector<string> classesToRegister = parseCsv(env, nativesClassesJString);
+    // Java system properties that contain LayoutLib config. The initial values in the map
+    // are the default values if the property is not specified.
+    std::unordered_map<std::string, std::string> systemProperties =
+            {{"core_native_classes", ""},
+             {"register_properties_during_load", ""},
+             {"icu.data.path", ""},
+             {"use_bridge_for_logging", ""},
+             {"keyboard_paths", ""}};
 
-    jstring registerProperty =
-            (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
-                                                 env->NewStringUTF(
-                                                         "register_properties_during_load"),
-                                                 env->NewStringUTF(""));
-    const char* registerPropertyString = env->GetStringUTFChars(registerProperty, 0);
-    if (strcmp(registerPropertyString, "true") == 0) {
+    for (auto& [name, defaultValue] : systemProperties) {
+        jstring propertyString =
+                (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
+                                                     env->NewStringUTF(name.c_str()),
+                                                     env->NewStringUTF(defaultValue.c_str()));
+        const char* propertyChars = env->GetStringUTFChars(propertyString, 0);
+        systemProperties[name] = string(propertyChars);
+        env->ReleaseStringUTFChars(propertyString, propertyChars);
+    }
+    // Get the names of classes that need to register their native methods
+    vector<string> classesToRegister = parseCsv(systemProperties["core_native_classes"]);
+
+    if (systemProperties["register_properties_during_load"] == "true") {
         // Set the system properties first as they could be used in the static initialization of
         // other classes
         if (register_android_os_SystemProperties(env) < 0) {
@@ -423,35 +423,20 @@
         env->CallStaticVoidMethod(bridge, setSystemPropertiesMethod);
         property_initialize_ro_cpu_abilist();
     }
-    env->ReleaseStringUTFChars(registerProperty, registerPropertyString);
 
     if (register_jni_procs(gRegJNIMap, classesToRegister, env) < 0) {
         return JNI_ERR;
     }
 
-    // Set the location of ICU data
-    auto stringPath = (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
-                                                           env->NewStringUTF("icu.data.path"),
-                                                           env->NewStringUTF(""));
-    const char* path = env->GetStringUTFChars(stringPath, 0);
-
-    if (strcmp(path, "**n/a**") != 0) {
-        bool icuInitialized = init_icu(path);
+    if (!systemProperties["register_properties_during_load"].empty()) {
+        // Set the location of ICU data
+        bool icuInitialized = init_icu(systemProperties["icu.data.path"].c_str());
         if (!icuInitialized) {
-            fprintf(stderr, "Failed to initialize ICU\n");
             return JNI_ERR;
         }
-    } else {
-        fprintf(stderr, "Skip initializing ICU\n");
     }
-    env->ReleaseStringUTFChars(stringPath, path);
 
-    jstring useJniProperty =
-            (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
-                                                 env->NewStringUTF("use_bridge_for_logging"),
-                                                 env->NewStringUTF(""));
-    const char* useJniString = env->GetStringUTFChars(useJniProperty, 0);
-    if (strcmp(useJniString, "true") == 0) {
+    if (systemProperties["use_bridge_for_logging"] == "true") {
         layoutLog = FindClassOrDie(env, "com/android/ide/common/rendering/api/ILayoutLog");
         layoutLog = MakeGlobalRefOrDie(env, layoutLog);
         logMethodId = GetMethodIDOrDie(env, layoutLog, "logAndroidFramework",
@@ -468,23 +453,16 @@
         // initialize logging, so ANDROD_LOG_TAGS env variable is respected
         android::base::InitLogging(nullptr, android::base::StderrLogger);
     }
-    env->ReleaseStringUTFChars(useJniProperty, useJniString);
 
     // Use English locale for number format to ensure correct parsing of floats when using strtof
     setlocale(LC_NUMERIC, "en_US.UTF-8");
 
-    auto keyboardPathsJString =
-            (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
-                                                 env->NewStringUTF("keyboard_paths"),
-                                                 env->NewStringUTF(""));
-    const char* keyboardPathsString = env->GetStringUTFChars(keyboardPathsJString, 0);
-    if (strcmp(keyboardPathsString, "**n/a**") != 0) {
-        vector<string> keyboardPaths = parseCsv(env, keyboardPathsJString);
+    if (!systemProperties["keyboard_paths"].empty()) {
+        vector<string> keyboardPaths = parseCsv(systemProperties["keyboard_paths"]);
         init_keyboard(env, keyboardPaths);
     } else {
         fprintf(stderr, "Skip initializing keyboard\n");
     }
-    env->ReleaseStringUTFChars(keyboardPathsJString, keyboardPathsString);
 
     return JNI_VERSION_1_6;
 }
diff --git a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
index 5a18d9e..b75d545 100644
--- a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
+++ b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
@@ -39,7 +39,7 @@
     optional string cur_token = 14;
     optional int32 cur_token_display_id = 15;
     optional bool system_ready = 16;
-    optional int32 last_switch_user_id = 17;
+    reserved 17; // deprecated last_switch_user_id
     optional bool have_connection = 18;
     optional bool bound_to_method = 19;
     optional bool is_interactive = 20;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 487b5be..ba9751f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8621,6 +8621,10 @@
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
 
+        <service android:name="com.android.system.virtualmachine.SecretkeeperJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE">
+        </service>
+
         <service android:name="com.android.server.PruneInstantAppsJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8edf42a..90f2731 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3348,26 +3348,23 @@
     <string name="config_carrierAppInstallDialogComponent" translatable="false"
             >com.android.simappdialog/com.android.simappdialog.InstallCarrierAppActivity</string>
 
-    <!-- Name of the default framework dialog that is used to get or save an app credential.
+    <!-- Name of the fallback CredentialManager dialog that is used to get or save an app
+     credential.
 
-    This UI should be always launch-able and is used as a fallback when an oem replacement activity
-    (defined at config_oemCredentialManagerDialogComponent) is undefined / not found. -->
-    <string name="config_credentialManagerDialogComponent" translatable="false"
+     If empty, no fallback will be used. IMPORTANT: In that case the OEM dialog value specified in
+     config_oemCredentialManagerDialogComponent must always launch-able. Otherwise, the
+     CredentialManager API contract is broken.
+
+     If specified, this UI should be always launch-able. It will be used as a fallback when the OEM
+     dialog value specified in config_oemCredentialManagerDialogComponent) is undefined / not
+     found. -->
+    <string name="config_fallbackCredentialManagerDialogComponent" translatable="false"
             >com.android.credentialmanager/com.android.credentialmanager.CredentialSelectorActivity</string>
-    <!-- Whether to allow the credential selector activity to be replaced by an activity at
-     run-time (restricted to the privileged activity specified by
-     config_credentialSelectorActivityName).
-
-     When disabled, the fallback activity defined at
-     config_credentialManagerDialogComponent will be used instead. -->
-    <bool name="config_enableOemCredentialManagerDialogComponent" translatable="false">true</bool>
     <!-- Fully qualified activity name providing the credential selector UI, that serves the
-     CredentialManager APIs.
+     CredentialManager APIs. Must be a system app component.
 
-     Used only when config_enableOemCredentialManagerDialogComponent is true.
-
-     If the activity specified cannot be found or launched, then the fallback activity defined at
-     config_credentialManagerDialogComponent will be used instead. -->
+     If empty, or if this activity specified cannot be found or launched, then the fallback activity
+     defined at config_fallbackCredentialManagerDialogComponent will be used instead. -->
     <string name="config_oemCredentialManagerDialogComponent" translatable="false"></string>
 
     <!-- Name of the broadcast receiver that is used to receive provider change events -->
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 660e4c0..6e56fe2 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -334,17 +334,9 @@
     <bool name="config_enable_cellular_on_boot_default">true</bool>
     <java-symbol type="bool" name="config_enable_cellular_on_boot_default" />
 
-    <!-- The network capabilities that would be forced marked as cellular transport regardless it's
-         on cellular or satellite-->
-    <string-array name="config_force_cellular_transport_capabilities">
-        <!-- Added the following three capabilities for now. For the long term solution, the client
-             requests satellite network should really include TRANSPORT_SATELLITE in the network
-             request. With the following workaround, the clients can continue request network with
-             the following capabilities with TRANSPORT_CELLULAR. The network with one of the
-             following capabilities would also be marked as cellular. -->
-        <item>ims</item>
-        <item>eims</item>
-        <item>xcap</item>
-    </string-array>
-    <java-symbol type="array" name="config_force_cellular_transport_capabilities" />
+    <!-- Defines metrics pull cooldown period. The default cooldown period is 23 hours,
+         some Telephony metrics need to be pulled more frequently  -->
+    <integer name="config_metrics_pull_cooldown_millis">82800000</integer>
+    <java-symbol type="integer" name="config_metrics_pull_cooldown_millis" />
+
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a180467..a025c8d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2297,8 +2297,7 @@
   <java-symbol type="string" name="config_customVpnAlwaysOnDisconnectedDialogComponent" />
   <java-symbol type="string" name="config_platformVpnConfirmDialogComponent" />
   <java-symbol type="string" name="config_carrierAppInstallDialogComponent" />
-  <java-symbol type="string" name="config_credentialManagerDialogComponent" />
-  <java-symbol type="bool" name="config_enableOemCredentialManagerDialogComponent" />
+  <java-symbol type="string" name="config_fallbackCredentialManagerDialogComponent" />
   <java-symbol type="string" name="config_oemCredentialManagerDialogComponent" />
   <java-symbol type="string" name="config_credentialManagerReceiverComponent" />
   <java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index e72beee..24031cad 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -101,6 +101,7 @@
         "flickerlib-trace_processor_shell",
         "mockito-target-extended-minus-junit4",
         "TestParameterInjector",
+        "android.content.res.flags-aconfig-java",
     ],
 
     libs: [
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index d115bf3..927c67c 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -21,16 +21,22 @@
 import static android.content.Intent.ACTION_VIEW;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
+import static com.android.window.flags.Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
 import android.annotation.NonNull;
@@ -46,6 +52,7 @@
 import android.app.servertransaction.ActivityRelaunchItem;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.ClientTransactionListenerController;
 import android.app.servertransaction.ConfigurationChangeItem;
 import android.app.servertransaction.NewIntentItem;
 import android.app.servertransaction.ResumeActivityItem;
@@ -60,7 +67,9 @@
 import android.hardware.display.VirtualDisplay;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.DisplayMetrics;
 import android.util.MergedConfiguration;
 import android.view.Display;
@@ -81,11 +90,14 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /**
@@ -103,11 +115,17 @@
     // few sequence numbers the framework used to launch the test activity.
     private static final int BASE_SEQ = 10000000;
 
-    @Rule
+    @Rule(order = 0)
     public final ActivityTestRule<TestActivity> mActivityTestRule =
             new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */,
                     false /* launchActivity */);
 
+    @Rule(order = 1)
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
+    @Mock
+    private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
+
     private WindowTokenClientController mOriginalWindowTokenClientController;
     private Configuration mOriginalAppConfig;
 
@@ -115,6 +133,8 @@
 
     @Before
     public void setup() {
+        MockitoAnnotations.initMocks(this);
+
         // Keep track of the original controller, so that it can be used to restore in tearDown()
         // when there is override in some test cases.
         mOriginalWindowTokenClientController = WindowTokenClientController.getInstance();
@@ -129,6 +149,8 @@
             mCreatedVirtualDisplays = null;
         }
         WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController);
+        ClientTransactionListenerController.getInstance()
+                .unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);
         InstrumentationRegistry.getInstrumentation().runOnMainSync(
                 () -> restoreConfig(ActivityThread.currentActivityThread(), mOriginalAppConfig));
     }
@@ -783,6 +805,101 @@
         verify(windowTokenClientController).onWindowContextWindowRemoved(clientToken);
     }
 
+    @Test
+    public void testActivityWindowInfoChanged_activityLaunch() {
+        mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+        ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
+                mActivityWindowInfoListener);
+
+        final Activity activity = mActivityTestRule.launchActivity(new Intent());
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity);
+
+        verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
+                activityClientRecord.getActivityWindowInfo());
+    }
+
+    @Test
+    public void testActivityWindowInfoChanged_activityRelaunch() throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+        ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
+                mActivityWindowInfoListener);
+
+        final Activity activity = mActivityTestRule.launchActivity(new Intent());
+        final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
+        appThread.scheduleTransaction(newRelaunchResumeTransaction(activity));
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity);
+
+        // The same ActivityWindowInfo won't trigger duplicated callback.
+        verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
+                activityClientRecord.getActivityWindowInfo());
+
+        final Configuration currentConfig = activity.getResources().getConfiguration();
+        final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+        activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
+                new Rect(0, 0, 1000, 1000));
+        final ActivityRelaunchItem relaunchItem = ActivityRelaunchItem.obtain(
+                activity.getActivityToken(), null, null, 0,
+                new MergedConfiguration(currentConfig, currentConfig),
+                false /* preserveWindow */, activityWindowInfo);
+        final ClientTransaction transaction = newTransaction(activity);
+        transaction.addTransactionItem(relaunchItem);
+        appThread.scheduleTransaction(transaction);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
+                activityWindowInfo);
+    }
+
+    @Test
+    public void testActivityWindowInfoChanged_activityConfigurationChanged()
+            throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+        ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
+                mActivityWindowInfoListener);
+
+        final Activity activity = mActivityTestRule.launchActivity(new Intent());
+        final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        clearInvocations(mActivityWindowInfoListener);
+        final Configuration config = new Configuration(activity.getResources().getConfiguration());
+        config.seq++;
+        final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+        final Rect taskFragmentBounds = new Rect(0, 0, 1000, 1000);
+        final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+        activityWindowInfo.set(true /* isEmbedded */, taskBounds, taskFragmentBounds);
+        final ActivityConfigurationChangeItem activityConfigurationChangeItem =
+                ActivityConfigurationChangeItem.obtain(
+                        activity.getActivityToken(), config, activityWindowInfo);
+        final ClientTransaction transaction = newTransaction(activity);
+        transaction.addTransactionItem(activityConfigurationChangeItem);
+        appThread.scheduleTransaction(transaction);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        verify(mActivityWindowInfoListener).accept(activity.getActivityToken(),
+                activityWindowInfo);
+
+        clearInvocations(mActivityWindowInfoListener);
+        final ActivityWindowInfo activityWindowInfo2 = new ActivityWindowInfo();
+        activityWindowInfo2.set(true /* isEmbedded */, taskBounds, taskFragmentBounds);
+        config.seq++;
+        final ActivityConfigurationChangeItem activityConfigurationChangeItem2 =
+                ActivityConfigurationChangeItem.obtain(
+                        activity.getActivityToken(), config, activityWindowInfo2);
+        final ClientTransaction transaction2 = newTransaction(activity);
+        transaction2.addTransactionItem(activityConfigurationChangeItem2);
+        appThread.scheduleTransaction(transaction);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        // The same ActivityWindowInfo won't trigger duplicated callback.
+        verify(mActivityWindowInfoListener, never()).accept(any(), any());
+    }
+
     /**
      * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord,
      * Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the
@@ -871,7 +988,7 @@
     @NonNull
     private static ClientTransaction newStopTransaction(@NonNull Activity activity) {
         final StopActivityItem stopStateRequest = StopActivityItem.obtain(
-                activity.getActivityToken(), 0 /* configChanges */);
+                activity.getActivityToken());
 
         final ClientTransaction transaction = newTransaction(activity);
         transaction.addTransactionItem(stopStateRequest);
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
index 4db5d1b..9907397 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -136,7 +136,7 @@
     @Test
     public void testDestroyActivityItem_preExecute() {
         final DestroyActivityItem item = DestroyActivityItem
-                .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+                .obtain(mActivityToken, false /* finished */);
         item.preExecute(mHandler);
 
         assertEquals(1, mActivitiesToBeDestroyed.size());
@@ -146,7 +146,7 @@
     @Test
     public void testDestroyActivityItem_postExecute() {
         final DestroyActivityItem item = DestroyActivityItem
-                .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+                .obtain(mActivityToken, false /* finished */);
         item.preExecute(mHandler);
         item.postExecute(mHandler, mPendingActions);
 
@@ -156,11 +156,11 @@
     @Test
     public void testDestroyActivityItem_execute() {
         final DestroyActivityItem item = DestroyActivityItem
-                .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+                .obtain(mActivityToken, false /* finished */);
         item.execute(mHandler, mActivityClientRecord, mPendingActions);
 
         verify(mHandler).handleDestroyActivity(eq(mActivityClientRecord), eq(false) /* finishing */,
-                eq(123) /* configChanges */, eq(false) /* getNonConfigInstance */, any());
+                eq(false) /* getNonConfigInstance */, any());
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 213fd7b..77d31a5 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -22,21 +22,29 @@
 
 import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.IDisplayManager;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.view.DisplayInfo;
+import android.window.ActivityWindowInfo;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.window.flags.Flags;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -44,6 +52,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.function.BiConsumer;
+
 /**
  * Tests for {@link ClientTransactionListenerController}.
  *
@@ -62,6 +72,10 @@
     private IDisplayManager mIDisplayManager;
     @Mock
     private DisplayManager.DisplayListener mListener;
+    @Mock
+    private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
+    @Mock
+    private IBinder mActivityToken;
 
     private DisplayManagerGlobal mDisplayManager;
     private Handler mHandler;
@@ -91,4 +105,24 @@
 
         verify(mListener).onDisplayChanged(123);
     }
+
+    @Test
+    public void testActivityWindowInfoChangedListener() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+        mController.registerActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+        final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+        activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
+                new Rect(0, 0, 1000, 1000));
+        mController.onActivityWindowInfoChanged(mActivityToken, activityWindowInfo);
+
+        verify(mActivityWindowInfoListener).accept(mActivityToken, activityWindowInfo);
+
+        clearInvocations(mActivityWindowInfoListener);
+        mController.unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+
+        mController.onActivityWindowInfoChanged(mActivityToken, activityWindowInfo);
+
+        verify(mActivityWindowInfoListener, never()).accept(any(), any());
+    }
 }
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 31ea675..584fe16 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -98,7 +98,7 @@
 
     @Test
     public void testRecycleDestroyActivityItem() {
-        testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true, 117));
+        testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true));
     }
 
     @Test
@@ -169,7 +169,7 @@
 
     @Test
     public void testRecyclePauseActivityItemItem() {
-        testRecycle(() -> PauseActivityItem.obtain(mActivityToken, true, true, 5, true, true));
+        testRecycle(() -> PauseActivityItem.obtain(mActivityToken, true, true, true, true));
     }
 
     @Test
@@ -185,7 +185,7 @@
 
     @Test
     public void testRecycleStopItem() {
-        testRecycle(() -> StopActivityItem.obtain(mActivityToken, 4));
+        testRecycle(() -> StopActivityItem.obtain(mActivityToken));
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index adb6f2a..935bc75 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -306,7 +306,7 @@
         final IBinder token = mock(IBinder.class);
         final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */);
         destroyTransaction.addTransactionItem(
-                DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */));
+                DestroyActivityItem.obtain(token, false /* finished */));
         destroyTransaction.preExecute(mTransactionHandler);
         // The activity should be added to to-be-destroyed container.
         assertEquals(1, activitiesToBeDestroyed.size());
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 75347bf..d451fe5 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -155,8 +155,7 @@
 
     @Test
     public void testDestroy() {
-        DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true /* finished */,
-                135 /* configChanges */);
+        DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true /* finished */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -244,8 +243,7 @@
     public void testPause() {
         // Write to parcel
         PauseActivityItem item = PauseActivityItem.obtain(mActivityToken, true /* finished */,
-                true /* userLeaving */, 135 /* configChanges */, true /* dontReport */,
-                true /* autoEnteringPip */);
+                true /* userLeaving */, true /* dontReport */, true /* autoEnteringPip */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -272,7 +270,7 @@
     @Test
     public void testStop() {
         // Write to parcel
-        StopActivityItem item = StopActivityItem.obtain(mActivityToken, 14 /* configChanges */);
+        StopActivityItem item = StopActivityItem.obtain(mActivityToken);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -305,8 +303,7 @@
         ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
                 mActivityToken, config(), new ActivityWindowInfo());
 
-        StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken,
-                78 /* configChanges */);
+        StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken);
 
         ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
         transaction.addTransactionItem(callback1);
@@ -351,8 +348,7 @@
         mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
 
         // Write to parcel
-        StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken,
-                78 /* configChanges */);
+        StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken);
 
         ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
         transaction.addTransactionItem(lifecycleRequest);
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 4a9cb71..0c1e879 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -18,34 +18,52 @@
 
 import android.annotation.NonNull;
 import android.app.ResourcesManager;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.LocaleList;
 import android.platform.test.annotations.Postsubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
 import android.view.Display;
 import android.view.DisplayAdjustments;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import junit.framework.TestCase;
 
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
 @Postsubmit
+@RunWith(AndroidJUnit4.class)
 public class ResourcesManagerTest extends TestCase {
     private static final int SECONDARY_DISPLAY_ID = 1;
     private static final String APP_ONE_RES_DIR = "app_one.apk";
     private static final String APP_ONE_RES_SPLIT_DIR = "app_one_split.apk";
     private static final String APP_TWO_RES_DIR = "app_two.apk";
     private static final String LIB_RES_DIR = "lib.apk";
+    private static final String TEST_LIB = "com.android.frameworks.coretests.bdr_helper_app1";
 
     private ResourcesManager mResourcesManager;
     private Map<Integer, DisplayMetrics> mDisplayMetricsMap;
+    private PackageManager mPackageManager;
 
-    @Override
-    protected void setUp() throws Exception {
+    @Before
+    public void setUp() throws Exception {
         super.setUp();
 
         mDisplayMetricsMap = new HashMap<>();
@@ -93,8 +111,14 @@
                 return mDisplayMetricsMap.get(displayId);
             }
         };
+
+        mPackageManager = InstrumentationRegistry.getContext().getPackageManager();
     }
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Test
     @SmallTest
     public void testMultipleCallsWithIdenticalParametersCacheReference() {
         Resources resources = mResourcesManager.getResources(
@@ -109,6 +133,7 @@
         assertSame(resources.getImpl(), newResources.getImpl());
     }
 
+    @Test
     @SmallTest
     public void testMultipleCallsWithDifferentParametersReturnDifferentReferences() {
         Resources resources = mResourcesManager.getResources(
@@ -125,6 +150,7 @@
         assertNotSame(resources, newResources);
     }
 
+    @Test
     @SmallTest
     public void testAddingASplitCreatesANewImpl() {
         Resources resources1 = mResourcesManager.getResources(
@@ -142,6 +168,7 @@
         assertNotSame(resources1.getImpl(), resources2.getImpl());
     }
 
+    @Test
     @SmallTest
     public void testUpdateConfigurationUpdatesAllAssetManagers() {
         Resources resources1 = mResourcesManager.getResources(
@@ -187,6 +214,7 @@
         assertEquals(expectedConfig, resources3.getConfiguration());
     }
 
+    @Test
     @SmallTest
     public void testTwoActivitiesWithIdenticalParametersShareImpl() {
         Binder activity1 = new Binder();
@@ -208,6 +236,7 @@
         assertSame(resources1.getImpl(), resources2.getImpl());
     }
 
+    @Test
     @SmallTest
     public void testThemesGetUpdatedWithNewImpl() {
         Binder activity1 = new Binder();
@@ -237,6 +266,7 @@
         assertTrue(value.data != 0);
     }
 
+    @Test
     @SmallTest
     public void testMultipleResourcesForOneActivityGetUpdatedWhenActivityBaseUpdates() {
         Binder activity1 = new Binder();
@@ -286,6 +316,7 @@
         assertEquals(expectedConfig2, resources2.getConfiguration());
     }
 
+    @Test
     @SmallTest
     public void testChangingActivityDisplayDoesntOverrideDisplayRequestedByResources() {
         Binder activity = new Binder();
@@ -322,4 +353,101 @@
         assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels,
                 defaultDisplayResources.getDisplayMetrics().widthPixels);
     }
+
+    @Test
+    @SmallTest
+    @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
+    public void testExistingResourcesAfterResourcePathsRegistration()
+             throws PackageManager.NameNotFoundException {
+        // Inject ResourcesManager instance from this test to the ResourcesManager class so that all
+        // the static method can interact with this test smoothly.
+        ResourcesManager oriResourcesManager = ResourcesManager.getInstance();
+        ResourcesManager.setInstance(mResourcesManager);
+
+        // Create a Resources before register resources' paths for a package.
+        Resources resources = mResourcesManager.getResources(
+                null, APP_ONE_RES_DIR, null, null, null, null, null, null,
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
+        assertNotNull(resources);
+        ResourcesImpl oriResImpl = resources.getImpl();
+
+        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0);
+        Resources.registerResourcePaths(TEST_LIB, appInfo);
+
+        assertNotSame(oriResImpl, resources.getImpl());
+
+        String[] resourcePaths = appInfo.getAllApkPaths();
+        resourcePaths = removeDuplicates(resourcePaths);
+        ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
+        assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
+
+        // Package resources' paths should be cached in ResourcesManager.
+        assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance()
+                .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths()));
+
+        // Revert the ResourcesManager instance back.
+        ResourcesManager.setInstance(oriResourcesManager);
+    }
+
+    @Test
+    @SmallTest
+    @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
+    public void testNewResourcesAfterResourcePathsRegistration()
+            throws PackageManager.NameNotFoundException {
+        // Inject ResourcesManager instance from this test to the ResourcesManager class so that all
+        // the static method can interact with this test smoothly.
+        ResourcesManager oriResourcesManager = ResourcesManager.getInstance();
+        ResourcesManager.setInstance(mResourcesManager);
+
+        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0);
+        Resources.registerResourcePaths(TEST_LIB, appInfo);
+
+        // Create a Resources after register resources' paths for a package.
+        Resources resources = mResourcesManager.getResources(
+                null, APP_ONE_RES_DIR, null, null, null, null, null, null,
+                CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
+        assertNotNull(resources);
+
+        String[] resourcePaths = appInfo.getAllApkPaths();
+        resourcePaths = removeDuplicates(resourcePaths);
+        ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
+        assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
+
+        // Package resources' paths should be cached in ResourcesManager.
+        assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance()
+                .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths()));
+
+        // Revert the ResourcesManager instance back.
+        ResourcesManager.setInstance(oriResourcesManager);
+    }
+
+    private static boolean allResourcePathsLoaded(String[] resourcePaths, ApkAssets[] loadedAsset) {
+        for (int i = 0; i < resourcePaths.length; i++) {
+            if (!resourcePaths[i].endsWith(".apk")) {
+                continue;
+            }
+            boolean found = false;
+            for (int j = 0; j < loadedAsset.length; j++) {
+                if (loadedAsset[j].getAssetPath().equals(resourcePaths[i])) {
+                    found = true;
+                }
+            }
+            if (!found) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static String[] removeDuplicates(String[] paths) {
+        var pathList = new ArrayList<String>();
+        var pathSet = new ArraySet<String>();
+        final int pathsLen = paths.length;
+        for (int i = 0; i < pathsLen; i++) {
+            if (pathSet.add(paths[i])) {
+                pathList.add(paths[i]);
+            }
+        }
+        return pathList.toArray(new String[0]);
+    }
 }
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
index 4f722ce..6ab77dc 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
@@ -355,7 +355,7 @@
         }
 
         @Override
-        public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+        public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) {
             RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
                     new RemoteViews.RemoteCollectionItems.Builder();
             itemsBuilder.setHasStableIds(hasStableIds())
diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
index 58cfc66..43e6227 100644
--- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
@@ -53,6 +53,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 
 import androidx.test.InstrumentationRegistry;
@@ -93,6 +94,9 @@
     private static final String TYPE_PLAIN_TEXT = "text/plain";
 
     private static UserInfo MANAGED_PROFILE_INFO = new UserInfo();
+    private static UserInfo PRIVATE_PROFILE_INFO = new UserInfo(12, "Private", null,
+            UserInfo.FLAG_PROFILE, UserManager.USER_TYPE_PROFILE_PRIVATE);
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     static {
         MANAGED_PROFILE_INFO.id = 10;
@@ -131,6 +135,7 @@
 
     @Before
     public void setup() {
+
         MockitoAnnotations.initMocks(this);
         mContext = InstrumentationRegistry.getTargetContext();
         sInjector = spy(new TestInjector());
@@ -632,6 +637,54 @@
                 logMakerCaptor.getValue().getSubtype());
     }
 
+    @Test
+    public void shouldForwardToParent_telephony_privateProfile() throws Exception {
+        mSetFlagsRule.enableFlags(
+                android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_INTENT_REDIRECTION);
+
+        sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
+        when(mIPm.canForwardTo(
+                any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);
+
+        List<UserInfo> profiles = new ArrayList<>();
+        profiles.add(CURRENT_USER_INFO);
+        profiles.add(PRIVATE_PROFILE_INFO);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+        when(mUserManager.getProfileParent(anyInt())).thenReturn(CURRENT_USER_INFO);
+        Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+        intent.setAction(Intent.ACTION_DIAL);
+        IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+        verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
+        assertEquals(activity.getStartActivityIntent().getAction(), intent.getAction());
+        assertEquals(activity.getUserIdActivityLaunchedIn(), CURRENT_USER_INFO.id);
+    }
+
+    @Test
+    public void shouldForwardToParent_mms_privateProfile() throws Exception {
+        mSetFlagsRule.enableFlags(
+                android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+                android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_INTENT_REDIRECTION);
+
+        sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
+        when(mIPm.canForwardTo(
+                any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);
+
+        List<UserInfo> profiles = new ArrayList<>();
+        profiles.add(CURRENT_USER_INFO);
+        profiles.add(PRIVATE_PROFILE_INFO);
+        when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+        when(mUserManager.getProfileParent(anyInt())).thenReturn(CURRENT_USER_INFO);
+        Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+        intent.setAction(Intent.ACTION_SEND);
+        intent.setType(TYPE_PLAIN_TEXT);
+        IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+        verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
+        assertEquals(activity.getStartActivityIntent().getAction(), intent.getAction());
+        assertEquals(activity.getStartActivityIntent().getType(), intent.getType());
+        assertEquals(activity.getUserIdActivityLaunchedIn(), CURRENT_USER_INFO.id);
+    }
+
     private void setupShouldSkipDisclosureTest() throws RemoteException {
         sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
         sActivityName = "MyTestActivity";
@@ -688,6 +741,14 @@
         protected MetricsLogger getMetricsLogger() {
             return mMetricsLogger;
         }
+
+        Intent getStartActivityIntent() {
+            return mStartActivityIntent;
+        }
+
+        int getUserIdActivityLaunchedIn() {
+            return mUserIdActivityLaunchedIn;
+        }
     }
 
     public class TestInjector implements IntentForwarderActivity.Injector {
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 66be05f..ed641e0 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -309,17 +309,17 @@
 
         private void pauseActivity(ActivityClientRecord r) {
             mThread.handlePauseActivity(r, false /* finished */,
-                    false /* userLeaving */, 0 /* configChanges */, false /* autoEnteringPip */,
+                    false /* userLeaving */, false /* autoEnteringPip */,
                     null /* pendingActions */, "test");
         }
 
         private void stopActivity(ActivityClientRecord r) {
-            mThread.handleStopActivity(r, 0 /* configChanges */,
+            mThread.handleStopActivity(r,
                     new PendingTransactionActions(), false /* finalStateRequest */, "test");
         }
 
         private void destroyActivity(ActivityClientRecord r) {
-            mThread.handleDestroyActivity(r, true /* finishing */, 0 /* configChanges */,
+            mThread.handleDestroyActivity(r, true /* finishing */,
                     false /* getNonConfigInstance */, "test");
         }
 
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 238a3e1..1410950 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -72,6 +72,12 @@
     src: "enhanced-confirmation.xml",
 }
 
+prebuilt_etc {
+    name: "package-shareduid-allowlist.xml",
+    sub_dir: "sysconfig",
+    src: "package-shareduid-allowlist.xml",
+}
+
 // Privapp permission whitelist files
 
 prebuilt_etc {
diff --git a/data/etc/CleanSpec.mk b/data/etc/CleanSpec.mk
index 783a7ed..fd38d27 100644
--- a/data/etc/CleanSpec.mk
+++ b/data/etc/CleanSpec.mk
@@ -43,6 +43,8 @@
 #$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
 #$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
 #$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/sysconfig/package-shareduid-allowlist.xml)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/sysconfig/package-shareduid-allowlist.xml)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.carrierconfig.xml)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/permissions/com.android.carrierconfig.xml)
 $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.emergency.xml)
diff --git a/data/etc/package-shareduid-allowlist.xml b/data/etc/package-shareduid-allowlist.xml
new file mode 100644
index 0000000..2401d4a
--- /dev/null
+++ b/data/etc/package-shareduid-allowlist.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+ -->
+
+<!--
+This XML defines an allowlist for packages that want to join a particular shared-uid.
+If a non-system package that is signed with platform signature, is trying to join a particular
+shared-uid, and not in this list, the installation will fail.
+
+- The "package" XML attribute refers to the app's package name.
+- The "shareduid" XML attribute refers to the shared uid name.
+
+Example usage
+    1. <allow-package-shareduid package="com.example.app" shareduid="android.uid.system"/>
+        Indicates that a package - com.example.app, will be able to join android.uid.system.
+    2. <allow-package-shareduid package="oem.example.app" shareduid="oem.uid.custom"/>
+        Indicates that a package - oem.example.app, will be able to join oem.uid.custom.
+-->
+
+<config>
+    <allow-package-shareduid package="android.test.settings" shareduid="android.uid.system" />
+</config>
diff --git a/data/fonts/font_fallback.xml b/data/fonts/font_fallback.xml
index 15ea15a..53024ab 100644
--- a/data/fonts/font_fallback.xml
+++ b/data/fonts/font_fallback.xml
@@ -792,14 +792,10 @@
         </font>
     </family>
     <family lang="ja">
-        <font weight="400" style="normal">
-            NotoSerifHentaigana-EL.ttf
+        <font postScriptName="NotoSerifHentaigana-ExtraLight" supportedAxes="wght">
+            NotoSerifHentaigana.ttf
             <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="700" style="normal">
-            NotoSerifHentaigana-EL.ttf
-          <axis tag="wght" stylevalue="700"/>
-        </font>
     </family>
     <family lang="ko">
         <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Regular">
diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml
index c1ca67e..ac1b064 100644
--- a/data/fonts/font_fallback_cjkvf.xml
+++ b/data/fonts/font_fallback_cjkvf.xml
@@ -804,14 +804,10 @@
         </font>
     </family>
     <family lang="ja">
-        <font weight="400" style="normal">
-            NotoSerifHentaigana-EL.ttf
+        <font postScriptName="NotoSerifHentaigana-ExtraLight" supportedAxes="wght">
+            NotoSerifHentaigana.ttf
             <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="700" style="normal">
-            NotoSerifHentaigana-EL.ttf
-            <axis tag="wght" stylevalue="700"/>
-        </font>
     </family>
     <family lang="ko">
         <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index b23f005..d1aa8e9 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -1433,12 +1433,12 @@
         </font>
     </family>
     <family lang="ja">
-        <font weight="400" style="normal">
-            NotoSerifHentaigana-EL.ttf
+        <font weight="400" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+            NotoSerifHentaigana.ttf
             <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="700" style="normal">
-            NotoSerifHentaigana-EL.ttf
+        <font weight="700" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+            NotoSerifHentaigana.ttf
             <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml
index 1ab71ae..9545ae7 100644
--- a/data/fonts/fonts_cjkvf.xml
+++ b/data/fonts/fonts_cjkvf.xml
@@ -1532,12 +1532,12 @@
         </font>
     </family>
     <family lang="ja">
-        <font weight="400" style="normal">
-            NotoSerifHentaigana-EL.ttf
+        <font weight="400" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+            NotoSerifHentaigana.ttf
             <axis tag="wght" stylevalue="400"/>
         </font>
-        <font weight="700" style="normal">
-            NotoSerifHentaigana-EL.ttf
+        <font weight="700" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+            NotoSerifHentaigana.ttf
             <axis tag="wght" stylevalue="700"/>
         </font>
     </family>
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 6da0719..884268a 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -29,11 +29,13 @@
 import com.android.internal.util.Preconditions;
 
 import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.NeverInline;
 
 import libcore.util.NativeAllocationRegistry;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Locale;
 import java.util.Objects;
 
 /**
@@ -85,6 +87,30 @@
         return mChars;
     }
 
+    private void rangeCheck(int start, int end) {
+        if (start < 0 || start > end || end > mChars.length) {
+            throwRangeError(start, end);
+        }
+    }
+
+    @NeverInline
+    private void throwRangeError(int start, int end) {
+        throw new IllegalArgumentException(String.format(Locale.US,
+            "start(%d) end(%d) length(%d) out of bounds", start, end, mChars.length));
+    }
+
+    private void offsetCheck(int offset) {
+        if (offset < 0 || offset >= mChars.length) {
+            throwOffsetError(offset);
+        }
+    }
+
+    @NeverInline
+    private void throwOffsetError(int offset) {
+        throw new IllegalArgumentException(String.format(Locale.US,
+            "offset (%d) length(%d) out of bounds", offset, mChars.length));
+    }
+
     /**
      * Returns the width of a given range.
      *
@@ -93,12 +119,7 @@
      */
     public @FloatRange(from = 0.0) @Px float getWidth(
             @IntRange(from = 0) int start, @IntRange(from = 0) int end) {
-        Preconditions.checkArgument(0 <= start && start <= mChars.length,
-                "start(%d) must be 0 <= start <= %d", start, mChars.length);
-        Preconditions.checkArgument(0 <= end && end <= mChars.length,
-                "end(%d) must be 0 <= end <= %d", end, mChars.length);
-        Preconditions.checkArgument(start <= end,
-                "start(%d) is larger than end(%d)", start, end);
+        rangeCheck(start, end);
         return nGetWidth(mNativePtr, start, end);
     }
 
@@ -120,12 +141,7 @@
      */
     public void getBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
             @NonNull Rect rect) {
-        Preconditions.checkArgument(0 <= start && start <= mChars.length,
-                "start(%d) must be 0 <= start <= %d", start, mChars.length);
-        Preconditions.checkArgument(0 <= end && end <= mChars.length,
-                "end(%d) must be 0 <= end <= %d", end, mChars.length);
-        Preconditions.checkArgument(start <= end,
-                "start(%d) is larger than end(%d)", start, end);
+        rangeCheck(start, end);
         Preconditions.checkNotNull(rect);
         nGetBounds(mNativePtr, mChars, start, end, rect);
     }
@@ -139,12 +155,7 @@
      */
     public void getFontMetricsInt(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
             @NonNull Paint.FontMetricsInt outMetrics) {
-        Preconditions.checkArgument(0 <= start && start <= mChars.length,
-                "start(%d) must be 0 <= start <= %d", start, mChars.length);
-        Preconditions.checkArgument(0 <= end && end <= mChars.length,
-                "end(%d) must be 0 <= end <= %d", end, mChars.length);
-        Preconditions.checkArgument(start <= end,
-                "start(%d) is larger than end(%d)", start, end);
+        rangeCheck(start, end);
         Objects.requireNonNull(outMetrics);
 
         long packed = nGetExtent(mNativePtr, mChars, start, end);
@@ -160,8 +171,7 @@
      * @param offset an offset of the character.
      */
     public @FloatRange(from = 0.0f) @Px float getCharWidthAt(@IntRange(from = 0) int offset) {
-        Preconditions.checkArgument(0 <= offset && offset < mChars.length,
-                "offset(%d) is larger than text length %d" + offset, mChars.length);
+        offsetCheck(offset);
         return nGetCharWidthAt(mNativePtr, offset);
     }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 038d008..1abda42 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -56,6 +56,7 @@
 import android.app.ActivityThread;
 import android.app.Application;
 import android.app.Instrumentation;
+import android.app.servertransaction.ClientTransactionListenerController;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -103,6 +104,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
 
 /**
  * Main controller class that manages split states and presentation.
@@ -178,6 +180,20 @@
 
     private final List<ActivityStack> mLastReportedActivityStacks = new ArrayList<>();
 
+    /** WM Jetpack set callback for {@link EmbeddedActivityWindowInfo}. */
+    @GuardedBy("mLock")
+    @Nullable
+    private Pair<Executor, Consumer<EmbeddedActivityWindowInfo>>
+            mEmbeddedActivityWindowInfoCallback;
+
+    /** Listener registered to {@link ClientTransactionListenerController}. */
+    @GuardedBy("mLock")
+    @Nullable
+    private final BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener =
+            Flags.activityWindowInfoFlag()
+                    ? this::onActivityWindowInfoChanged
+                    : null;
+
     private final Handler mHandler;
     final Object mLock = new Object();
     private final ActivityStartMonitor mActivityStartMonitor;
@@ -2456,6 +2472,13 @@
     }
 
     @VisibleForTesting
+    @Nullable
+    ActivityThread.ActivityClientRecord getActivityClientRecord(@NonNull Activity activity) {
+        return ActivityThread.currentActivityThread()
+                .getActivityClient(activity.getActivityToken());
+    }
+
+    @VisibleForTesting
     ActivityStartMonitor getActivityStartMonitor() {
         return mActivityStartMonitor;
     }
@@ -2468,8 +2491,7 @@
     @VisibleForTesting
     @Nullable
     IBinder getTaskFragmentTokenFromActivityClientRecord(@NonNull Activity activity) {
-        final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread()
-                .getActivityClient(activity.getActivityToken());
+        final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
         return record != null ? record.mTaskFragmentToken : null;
     }
 
@@ -2876,17 +2898,102 @@
         }
     }
 
+    @Override
+    public void setEmbeddedActivityWindowInfoCallback(@NonNull Executor executor,
+            @NonNull Consumer<EmbeddedActivityWindowInfo> callback) {
+        if (!Flags.activityWindowInfoFlag()) {
+            return;
+        }
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        synchronized (mLock) {
+            if (mEmbeddedActivityWindowInfoCallback == null) {
+                ClientTransactionListenerController.getInstance()
+                        .registerActivityWindowInfoChangedListener(getActivityWindowInfoListener());
+            }
+            mEmbeddedActivityWindowInfoCallback = new Pair<>(executor, callback);
+        }
+    }
+
+    @Override
+    public void clearEmbeddedActivityWindowInfoCallback() {
+        if (!Flags.activityWindowInfoFlag()) {
+            return;
+        }
+        synchronized (mLock) {
+            if (mEmbeddedActivityWindowInfoCallback == null) {
+                return;
+            }
+            mEmbeddedActivityWindowInfoCallback = null;
+            ClientTransactionListenerController.getInstance()
+                    .unregisterActivityWindowInfoChangedListener(getActivityWindowInfoListener());
+        }
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mLock")
     @Nullable
-    private static ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
+    BiConsumer<IBinder, ActivityWindowInfo> getActivityWindowInfoListener() {
+        return mActivityWindowInfoListener;
+    }
+
+    @Nullable
+    @Override
+    public EmbeddedActivityWindowInfo getEmbeddedActivityWindowInfo(@NonNull Activity activity) {
+        if (!Flags.activityWindowInfoFlag()) {
+            return null;
+        }
+        synchronized (mLock) {
+            final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
+            return activityWindowInfo != null
+                    ? translateActivityWindowInfo(activity, activityWindowInfo)
+                    : null;
+        }
+    }
+
+    @VisibleForTesting
+    void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
+            @NonNull ActivityWindowInfo activityWindowInfo) {
+        synchronized (mLock) {
+            if (mEmbeddedActivityWindowInfoCallback == null) {
+                return;
+            }
+            final Executor executor = mEmbeddedActivityWindowInfoCallback.first;
+            final Consumer<EmbeddedActivityWindowInfo> callback =
+                    mEmbeddedActivityWindowInfoCallback.second;
+
+            final Activity activity = getActivity(activityToken);
+            if (activity == null) {
+                return;
+            }
+            final EmbeddedActivityWindowInfo info = translateActivityWindowInfo(
+                    activity, activityWindowInfo);
+
+            executor.execute(() -> callback.accept(info));
+        }
+    }
+
+    @Nullable
+    private ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
         if (activity.isFinishing()) {
             return null;
         }
-        final ActivityThread.ActivityClientRecord record =
-                ActivityThread.currentActivityThread()
-                        .getActivityClient(activity.getActivityToken());
+        final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
         return record != null ? record.getActivityWindowInfo() : null;
     }
 
+    @NonNull
+    private static EmbeddedActivityWindowInfo translateActivityWindowInfo(
+            @NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) {
+        final boolean isEmbedded = activityWindowInfo.isEmbedded();
+        final Rect activityBounds = new Rect(activity.getResources().getConfiguration()
+                .windowConfiguration.getBounds());
+        final Rect taskBounds = new Rect(activityWindowInfo.getTaskBounds());
+        final Rect activityStackBounds = new Rect(activityWindowInfo.getTaskFragmentBounds());
+        return new EmbeddedActivityWindowInfo(activity, isEmbedded, activityBounds, taskBounds,
+                activityStackBounds);
+    }
+
     /**
      * If the two rules have the same presentation, and the calculated {@link SplitAttributes}
      * matches the {@link SplitAttributes} of {@link SplitContainer}, we can reuse the same
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 00f8b59..bdeeb73 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -72,6 +72,8 @@
 import android.annotation.NonNull;
 import android.app.Activity;
 import android.app.ActivityOptions;
+import android.app.ActivityThread;
+import android.app.servertransaction.ClientTransactionListenerController;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -83,9 +85,11 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.ArraySet;
 import android.view.WindowInsets;
 import android.view.WindowMetrics;
+import android.window.ActivityWindowInfo;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOrganizer;
 import android.window.TaskFragmentParentInfo;
@@ -99,7 +103,10 @@
 import androidx.window.extensions.layout.WindowLayoutComponentImpl;
 import androidx.window.extensions.layout.WindowLayoutInfo;
 
+import com.android.window.flags.Flags;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -110,6 +117,8 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 /**
@@ -127,6 +136,9 @@
     private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent(
             new ComponentName("test", "placeholder"));
 
+    @Rule
+    public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
+
     private Activity mActivity;
     @Mock
     private Resources mActivityResources;
@@ -138,6 +150,13 @@
     private Handler mHandler;
     @Mock
     private WindowLayoutComponentImpl mWindowLayoutComponent;
+    @Mock
+    private ActivityWindowInfo mActivityWindowInfo;
+    @Mock
+    private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
+    @Mock
+    private androidx.window.extensions.core.util.function.Consumer<EmbeddedActivityWindowInfo>
+            mEmbeddedActivityWindowInfoCallback;
 
     private SplitController mSplitController;
     private SplitPresenter mSplitPresenter;
@@ -1529,6 +1548,73 @@
                 .getTopNonFinishingActivity(), secondaryActivity);
     }
 
+    @Test
+    public void testIsActivityEmbedded() {
+        mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+        assertFalse(mSplitController.isActivityEmbedded(mActivity));
+
+        doReturn(true).when(mActivityWindowInfo).isEmbedded();
+
+        assertTrue(mSplitController.isActivityEmbedded(mActivity));
+    }
+
+    @Test
+    public void testGetEmbeddedActivityWindowInfo() {
+        mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+        final boolean isEmbedded = true;
+        final Rect activityBounds = mActivity.getResources().getConfiguration().windowConfiguration
+                .getBounds();
+        final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+        final Rect activityStackBounds = new Rect(0, 0, 500, 2000);
+        doReturn(isEmbedded).when(mActivityWindowInfo).isEmbedded();
+        doReturn(taskBounds).when(mActivityWindowInfo).getTaskBounds();
+        doReturn(activityStackBounds).when(mActivityWindowInfo).getTaskFragmentBounds();
+
+        final EmbeddedActivityWindowInfo expected = new EmbeddedActivityWindowInfo(mActivity,
+                isEmbedded, activityBounds, taskBounds, activityStackBounds);
+        assertEquals(expected, mSplitController.getEmbeddedActivityWindowInfo(mActivity));
+    }
+
+    @Test
+    public void testSetEmbeddedActivityWindowInfoCallback() {
+        mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+        final ClientTransactionListenerController controller = ClientTransactionListenerController
+                .getInstance();
+        spyOn(controller);
+        doNothing().when(controller).registerActivityWindowInfoChangedListener(any());
+        doReturn(mActivityWindowInfoListener).when(mSplitController)
+                .getActivityWindowInfoListener();
+        final Executor executor = Runnable::run;
+
+        // Register to ClientTransactionListenerController
+        mSplitController.setEmbeddedActivityWindowInfoCallback(executor,
+                mEmbeddedActivityWindowInfoCallback);
+
+        verify(controller).registerActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+        verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any());
+
+        // Test onActivityWindowInfoChanged triggered.
+        mSplitController.onActivityWindowInfoChanged(mActivity.getActivityToken(),
+                mActivityWindowInfo);
+
+        verify(mEmbeddedActivityWindowInfoCallback).accept(any());
+
+        // Unregister to ClientTransactionListenerController
+        mSplitController.clearEmbeddedActivityWindowInfoCallback();
+
+        verify(controller).unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+
+        // Test onActivityWindowInfoChanged triggered as no-op after clear callback.
+        clearInvocations(mEmbeddedActivityWindowInfoCallback);
+        mSplitController.onActivityWindowInfoChanged(mActivity.getActivityToken(),
+                mActivityWindowInfo);
+
+        verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any());
+    }
+
     /** Creates a mock activity in the organizer process. */
     private Activity createMockActivity() {
         return createMockActivity(TASK_ID);
@@ -1537,13 +1623,17 @@
     /** Creates a mock activity in the organizer process. */
     private Activity createMockActivity(int taskId) {
         final Activity activity = mock(Activity.class);
+        final ActivityThread.ActivityClientRecord activityClientRecord =
+                mock(ActivityThread.ActivityClientRecord.class);
         doReturn(mActivityResources).when(activity).getResources();
         final IBinder activityToken = new Binder();
         doReturn(activityToken).when(activity).getActivityToken();
         doReturn(activity).when(mSplitController).getActivity(activityToken);
+        doReturn(activityClientRecord).when(mSplitController).getActivityClientRecord(activity);
         doReturn(taskId).when(activity).getTaskId();
         doReturn(new ActivityInfo()).when(activity).getActivityInfo();
         doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
+        doReturn(mActivityWindowInfo).when(activityClientRecord).getActivityWindowInfo();
         return activity;
     }
 
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 8baaf2f..a541c59 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -145,4 +145,7 @@
 
     <!-- Whether CompatUIController is enabled -->
     <bool name="config_enableCompatUIController">true</bool>
+
+    <!-- Whether pointer pilfer is required to start back animation. -->
+    <bool name="config_backAnimationRequiresPointerPilfer">true</bool>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 74967ef0..7dd3961 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -510,10 +510,6 @@
     split select if dragged until the touch input is within the range. -->
     <dimen name="desktop_mode_transition_area_width">32dp</dimen>
 
-    <!-- The height of the area at the top of the screen where a freeform task will transition to
-    fullscreen if dragged until the top bound of the task is within the area. -->
-    <dimen name="desktop_mode_transition_area_height">16dp</dimen>
-
     <!-- The width of the area where a desktop task will transition to fullscreen. -->
     <dimen name="desktop_mode_fullscreen_from_desktop_width">80dp</dimen>
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 2606fb6..9bd8531 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -64,6 +64,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.view.AppearanceRegion;
+import com.android.wm.shell.R;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
@@ -115,6 +116,7 @@
     private boolean mShouldStartOnNextMoveEvent = false;
     private boolean mOnBackStartDispatched = false;
     private boolean mPointerPilfered = false;
+    private final boolean mRequirePointerPilfer;
 
     private final FlingAnimationUtils mFlingAnimationUtils;
 
@@ -220,6 +222,8 @@
         mActivityTaskManager = activityTaskManager;
         mContext = context;
         mContentResolver = contentResolver;
+        mRequirePointerPilfer =
+                context.getResources().getBoolean(R.bool.config_backAnimationRequiresPointerPilfer);
         mBgHandler = bgHandler;
         shellInit.addInitCallback(this::onInit, this);
         mAnimationBackground = backAnimationBackground;
@@ -560,7 +564,9 @@
     private void tryDispatchOnBackStarted(
             IOnBackInvokedCallback callback,
             BackMotionEvent backEvent) {
-        if (mOnBackStartDispatched || callback == null || !mPointerPilfered) {
+        if (mOnBackStartDispatched
+                || callback == null
+                || (!mPointerPilfered && mRequirePointerPilfer)) {
             return;
         }
         dispatchOnBackStarted(callback, backEvent);
@@ -1006,6 +1012,8 @@
         pw.println(prefix + "  mBackGestureStarted=" + mBackGestureStarted);
         pw.println(prefix + "  mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress);
         pw.println(prefix + "  mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent);
+        pw.println(prefix + "  mPointerPilfered=" + mPointerPilfered);
+        pw.println(prefix + "  mRequirePointerPilfer=" + mRequirePointerPilfer);
         pw.println(prefix + "  mCurrentTracker state:");
         mCurrentTracker.dump(pw, prefix + "    ");
         pw.println(prefix + "  mQueuedTracker state:");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 7091c4b..fb0ed15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -98,6 +98,7 @@
      * Based on the coordinates of the current drag event, determine which indicator type we should
      * display, including no visible indicator.
      */
+    @NonNull
     IndicatorType updateIndicatorType(PointF inputCoordinates, int windowingMode) {
         final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
         // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
@@ -136,18 +137,18 @@
     Region calculateFullscreenRegion(DisplayLayout layout,
             @WindowConfiguration.WindowingMode int windowingMode, int captionHeight) {
         final Region region = new Region();
-        int edgeTransitionHeight = mContext.getResources().getDimensionPixelSize(
-                com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
+        int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
+                ? 2 * layout.stableInsets().top
+                : mContext.getResources().getDimensionPixelSize(
+                        com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
         // A thin, short Rect at the top of the screen.
         if (windowingMode == WINDOWING_MODE_FREEFORM) {
             int fromFreeformWidth = mContext.getResources().getDimensionPixelSize(
                     com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_width);
-            int fromFreeformHeight = mContext.getResources().getDimensionPixelSize(
-                    com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
             region.union(new Rect((layout.width() / 2) - (fromFreeformWidth / 2),
                     -captionHeight,
                     (layout.width() / 2) + (fromFreeformWidth / 2),
-                    fromFreeformHeight));
+                    transitionHeight));
         }
         // A screen-wide, shorter Rect if the task is in fullscreen or split.
         if (windowingMode == WINDOWING_MODE_FULLSCREEN
@@ -155,7 +156,7 @@
             region.union(new Rect(0,
                     -captionHeight,
                     layout.width(),
-                    edgeTransitionHeight));
+                    transitionHeight));
         }
         return region;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index d1328ca..654409f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -140,7 +140,7 @@
 
     private val transitionAreaHeight
         get() = context.resources.getDimensionPixelSize(
-                com.android.wm.shell.R.dimen.desktop_mode_transition_area_height
+                com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height
         )
 
     private val transitionAreaWidth
@@ -565,30 +565,7 @@
      * @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to.
      */
     fun snapToHalfScreen(taskInfo: RunningTaskInfo, position: SnapPosition) {
-        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
-
-        val stableBounds = Rect()
-        displayLayout.getStableBounds(stableBounds)
-
-        val destinationWidth = stableBounds.width() / 2
-        val destinationBounds = when (position) {
-            SnapPosition.LEFT -> {
-                Rect(
-                        stableBounds.left,
-                        stableBounds.top,
-                        stableBounds.left + destinationWidth,
-                        stableBounds.bottom
-                )
-            }
-            SnapPosition.RIGHT -> {
-                Rect(
-                        stableBounds.right - destinationWidth,
-                        stableBounds.top,
-                        stableBounds.right,
-                        stableBounds.bottom
-                )
-            }
-        }
+        val destinationBounds = getSnapBounds(taskInfo, position)
 
         if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) return
 
@@ -609,8 +586,35 @@
         outBounds.set(0, 0, desiredWidth, desiredHeight)
         // Center the task in screen bounds
         outBounds.offset(
-                screenBounds.centerX() - outBounds.centerX(),
-                screenBounds.centerY() - outBounds.centerY())
+            screenBounds.centerX() - outBounds.centerX(),
+            screenBounds.centerY() - outBounds.centerY())
+    }
+
+    private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect {
+        val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return Rect()
+
+        val stableBounds = Rect()
+        displayLayout.getStableBounds(stableBounds)
+
+        val destinationWidth = stableBounds.width() / 2
+        return when (position) {
+            SnapPosition.LEFT -> {
+                Rect(
+                    stableBounds.left,
+                    stableBounds.top,
+                    stableBounds.left + destinationWidth,
+                    stableBounds.bottom
+                )
+            }
+            SnapPosition.RIGHT -> {
+                Rect(
+                    stableBounds.right - destinationWidth,
+                    stableBounds.top,
+                    stableBounds.right,
+                    stableBounds.bottom
+                )
+            }
+        }
     }
 
     /**
@@ -646,7 +650,7 @@
             ?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) }
     }
 
-    private fun releaseVisualIndicator() {
+    fun releaseVisualIndicator() {
         val t = SurfaceControl.Transaction()
         visualIndicator?.releaseVisualIndicator(t)
         visualIndicator = null
@@ -927,16 +931,13 @@
         taskSurface: SurfaceControl,
         inputX: Float,
         taskTop: Float
-    ) {
+    ): DesktopModeVisualIndicator.IndicatorType {
         // If the visual indicator does not exist, create it.
-        if (visualIndicator == null) {
-            visualIndicator = DesktopModeVisualIndicator(
-                syncQueue, taskInfo, displayController, context, taskSurface,
-                rootTaskDisplayAreaOrganizer)
-        }
-        // Then, update the indicator type.
-        val indicator = visualIndicator ?: return
-        indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode)
+        val indicator = visualIndicator ?: DesktopModeVisualIndicator(
+            syncQueue, taskInfo, displayController, context, taskSurface,
+            rootTaskDisplayAreaOrganizer)
+        if (visualIndicator == null) visualIndicator = indicator
+        return indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode)
     }
 
     /**
@@ -956,20 +957,28 @@
         if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
             return
         }
-        if (taskBounds.top <= transitionAreaHeight) {
-            moveToFullscreenWithAnimation(taskInfo, position)
-            return
-        }
-        if (inputCoordinate.x <= transitionAreaWidth) {
-            releaseVisualIndicator()
-            snapToHalfScreen(taskInfo, SnapPosition.LEFT)
-            return
-        }
-        if (inputCoordinate.x >= (displayController.getDisplayLayout(taskInfo.displayId)?.width()
-            ?.minus(transitionAreaWidth) ?: return)) {
-            releaseVisualIndicator()
-            snapToHalfScreen(taskInfo, SnapPosition.RIGHT)
-            return
+
+        val indicator = visualIndicator ?: return
+        val indicatorType = indicator.updateIndicatorType(
+            PointF(inputCoordinate.x, taskBounds.top.toFloat()),
+            taskInfo.windowingMode
+        )
+        when (indicatorType) {
+            DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+                moveToFullscreenWithAnimation(taskInfo, position)
+            }
+            DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
+                releaseVisualIndicator()
+                snapToHalfScreen(taskInfo, SnapPosition.LEFT)
+            }
+            DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
+                releaseVisualIndicator()
+                snapToHalfScreen(taskInfo, SnapPosition.RIGHT)
+            }
+            DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
+            DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR -> {
+                releaseVisualIndicator()
+            }
         }
         // A freeform drag-move ended, remove the indicator immediately.
         releaseVisualIndicator()
@@ -982,14 +991,28 @@
      * @param y height of drag, to be checked against status bar height.
      */
     fun onDragPositioningEndThroughStatusBar(
+            inputCoordinates: PointF,
             taskInfo: RunningTaskInfo,
             freeformBounds: Rect
     ) {
-        finalizeDragToDesktop(taskInfo, freeformBounds)
-    }
-
-    private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
-        return displayController.getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
+        val indicator = visualIndicator ?: return
+        val indicatorType = indicator
+            .updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
+        when (indicatorType) {
+            DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
+                finalizeDragToDesktop(taskInfo, freeformBounds)
+            }
+            DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
+                    DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+                cancelDragToDesktop(taskInfo)
+            }
+            DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
+                finalizeDragToDesktop(taskInfo, getSnapBounds(taskInfo, SnapPosition.LEFT))
+            }
+            DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
+                finalizeDragToDesktop(taskInfo, getSnapBounds(taskInfo, SnapPosition.RIGHT))
+            }
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 2cdec81..4d47ca9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -122,6 +122,8 @@
     private static final long PIP_KEEP_CLEAR_AREAS_DELAY =
             SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200);
 
+    private static final long ENABLE_TOUCH_DELAY_MS = 200L;
+
     private Context mContext;
     protected ShellExecutor mMainExecutor;
     private DisplayController mDisplayController;
@@ -152,6 +154,8 @@
     private final Runnable mMovePipInResponseToKeepClearAreasChangeCallback =
             this::onKeepClearAreasChangedCallback;
 
+    private final Runnable mEnableTouchCallback = () -> mTouchHandler.setTouchEnabled(true);
+
     private void onKeepClearAreasChangedCallback() {
         if (mIsKeyguardShowingOrAnimating) {
             // early bail out if the change was caused by keyguard showing up
@@ -1037,6 +1041,7 @@
             saveReentryState(pipBounds);
         }
         // Disable touches while the animation is running
+        mMainExecutor.removeCallbacks(mEnableTouchCallback);
         mTouchHandler.setTouchEnabled(false);
         if (mPinnedStackAnimationRecentsCallback != null) {
             mPinnedStackAnimationRecentsCallback.onPipAnimationStarted();
@@ -1067,7 +1072,7 @@
         InteractionJankMonitor.getInstance().end(CUJ_PIP_TRANSITION);
 
         // Re-enable touches after the animation completes
-        mTouchHandler.setTouchEnabled(true);
+        mMainExecutor.executeDelayed(mEnableTouchCallback, ENABLE_TOUCH_DELAY_MS);
         mTouchHandler.onPinnedStackAnimationEnded(direction);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index fa3d8a6..f4ccd68 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -30,6 +30,10 @@
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
 import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION;
 import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFORM_SCALE;
 
@@ -81,6 +85,7 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
@@ -119,9 +124,8 @@
     private final Choreographer mMainChoreographer;
     private final DisplayController mDisplayController;
     private final SyncTransactionQueue mSyncQueue;
-    private final Optional<DesktopTasksController> mDesktopTasksController;
+    private final DesktopTasksController mDesktopTasksController;
     private final InputManager mInputManager;
-
     private boolean mTransitionDragActive;
 
     private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -129,8 +133,7 @@
     private final ExclusionRegionListener mExclusionRegionListener =
             new ExclusionRegionListenerImpl();
 
-    private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId =
-            new SparseArray<>();
+    private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId;
     private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
     private final InputMonitorFactory mInputMonitorFactory;
     private TaskOperations mTaskOperations;
@@ -197,7 +200,8 @@
                 new DesktopModeWindowDecoration.Factory(),
                 new InputMonitorFactory(),
                 SurfaceControl.Transaction::new,
-                rootTaskDisplayAreaOrganizer);
+                rootTaskDisplayAreaOrganizer,
+                new SparseArray<>());
     }
 
     @VisibleForTesting
@@ -219,7 +223,8 @@
             DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
             InputMonitorFactory inputMonitorFactory,
             Supplier<SurfaceControl.Transaction> transactionFactory,
-            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+            SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId) {
         mContext = context;
         mMainExecutor = shellExecutor;
         mMainHandler = mainHandler;
@@ -231,7 +236,7 @@
         mDisplayInsetsController = displayInsetsController;
         mSyncQueue = syncQueue;
         mTransitions = transitions;
-        mDesktopTasksController = desktopTasksController;
+        mDesktopTasksController = desktopTasksController.get();
         mShellCommandHandler = shellCommandHandler;
         mWindowManager = windowManager;
         mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
@@ -239,6 +244,7 @@
         mTransactionFactory = transactionFactory;
         mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
         mInputManager = mContext.getSystemService(InputManager.class);
+        mWindowDecorByTaskId = windowDecorByTaskId;
 
         shellInit.addInitCallback(this::onInit, this);
     }
@@ -248,8 +254,8 @@
         mShellCommandHandler.addDumpCallback(this::dump, this);
         mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
                 new DesktopModeOnInsetsChangedListener());
-        mDesktopTasksController.ifPresent(c -> c.setOnTaskResizeAnimationListener(
-                new DeskopModeOnTaskResizeAnimationListener()));
+        mDesktopTasksController.setOnTaskResizeAnimationListener(
+                new DeskopModeOnTaskResizeAnimationListener());
         try {
             mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener,
                     mContext.getDisplayId());
@@ -273,7 +279,7 @@
                     DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
                     if (decor != null && DesktopModeStatus.isEnabled()
                             && decor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
-                        mDesktopTasksController.ifPresent(c -> c.moveToSplit(decor.mTaskInfo));
+                        mDesktopTasksController.moveToSplit(decor.mTaskInfo);
                     }
                 }
             }
@@ -340,8 +346,7 @@
 
     @Override
     public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
-        final DesktopModeWindowDecoration decoration =
-                mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
+        final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
         if (decoration == null) return;
 
         decoration.close();
@@ -349,6 +354,10 @@
         if (mEventReceiversByDisplay.contains(displayId)) {
             removeTaskFromEventReceiver(displayId);
         }
+        // Remove the decoration from the cache last because WindowDecoration#close could still
+        // issue CANCEL MotionEvents to touch listeners before its view host is released.
+        // See b/327664694.
+        mWindowDecorByTaskId.remove(taskInfo.taskId);
     }
 
     private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
@@ -414,13 +423,11 @@
                     decoration.closeHandleMenu();
                 }
             } else if (id == R.id.desktop_button) {
-                if (mDesktopTasksController.isPresent()) {
-                    final WindowContainerTransaction wct = new WindowContainerTransaction();
-                    // App sometimes draws before the insets from WindowDecoration#relayout have
-                    // been added, so they must be added here
-                    mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
-                    mDesktopTasksController.get().moveToDesktop(mTaskId, wct);
-                }
+                final WindowContainerTransaction wct = new WindowContainerTransaction();
+                // App sometimes draws before the insets from WindowDecoration#relayout have
+                // been added, so they must be added here
+                mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
+                mDesktopTasksController.moveToDesktop(mTaskId, wct);
                 decoration.closeHandleMenu();
             } else if (id == R.id.fullscreen_button) {
                 decoration.closeHandleMenu();
@@ -428,42 +435,37 @@
                     mSplitScreenController.moveTaskToFullscreen(mTaskId,
                             SplitScreenController.EXIT_REASON_DESKTOP_MODE);
                 } else {
-                    mDesktopTasksController.ifPresent(c ->
-                            c.moveToFullscreen(mTaskId));
+                    mDesktopTasksController.moveToFullscreen(mTaskId);
                 }
             } else if (id == R.id.split_screen_button) {
                 decoration.closeHandleMenu();
-                mDesktopTasksController.ifPresent(c -> {
-                    c.requestSplit(decoration.mTaskInfo);
-                });
+                mDesktopTasksController.requestSplit(decoration.mTaskInfo);
             } else if (id == R.id.collapse_menu_button) {
                 decoration.closeHandleMenu();
             } else if (id == R.id.select_button) {
                 if (DesktopModeStatus.IS_DISPLAY_CHANGE_ENABLED) {
                     // TODO(b/278084491): dev option to enable display switching
                     //  remove when select is implemented
-                    mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId));
+                    mDesktopTasksController.moveToNextDisplay(mTaskId);
                 }
             } else if (id == R.id.maximize_window) {
                 final RunningTaskInfo taskInfo = decoration.mTaskInfo;
                 decoration.closeHandleMenu();
                 decoration.closeMaximizeMenu();
-                mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo));
+                mDesktopTasksController.toggleDesktopTaskSize(taskInfo);
             } else if (id == R.id.maximize_menu_maximize_button) {
                 final RunningTaskInfo taskInfo = decoration.mTaskInfo;
-                mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo));
+                mDesktopTasksController.toggleDesktopTaskSize(taskInfo);
                 decoration.closeHandleMenu();
                 decoration.closeMaximizeMenu();
             } else if (id == R.id.maximize_menu_snap_left_button) {
                 final RunningTaskInfo taskInfo = decoration.mTaskInfo;
-                mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
-                        taskInfo, SnapPosition.LEFT));
+                mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.LEFT);
                 decoration.closeHandleMenu();
                 decoration.closeMaximizeMenu();
             } else if (id == R.id.maximize_menu_snap_right_button) {
                 final RunningTaskInfo taskInfo = decoration.mTaskInfo;
-                mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
-                        taskInfo, SnapPosition.RIGHT));
+                mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.RIGHT);
                 decoration.closeHandleMenu();
                 decoration.closeMaximizeMenu();
             }
@@ -572,7 +574,7 @@
 
         private void moveTaskToFront(RunningTaskInfo taskInfo) {
             if (!taskInfo.isFocused) {
-                mDesktopTasksController.ifPresent(c -> c.moveTaskToFront(taskInfo));
+                mDesktopTasksController.moveTaskToFront(taskInfo);
             }
         }
 
@@ -616,10 +618,10 @@
                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
                     final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningMove(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
-                    mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
+                    mDesktopTasksController.onDragPositioningMove(taskInfo,
                             decoration.mTaskSurface,
                             e.getRawX(dragPointerIdx),
-                            newTaskBounds));
+                            newTaskBounds);
                     mIsDragging = true;
                     return true;
                 }
@@ -641,10 +643,9 @@
                             (int) (e.getRawY(dragPointerIdx) - e.getY(dragPointerIdx)));
                     final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
-                    mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
-                            position,
+                    mDesktopTasksController.onDragPositioningEnd(taskInfo, position,
                             new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
-                            newTaskBounds));
+                            newTaskBounds);
                     if (touchingButton && !mHasLongClicked) {
                         // We need the input event to not be consumed here to end the ripple
                         // effect on the touched button. We will reset drag state in the ensuing
@@ -672,10 +673,8 @@
                     && action != MotionEvent.ACTION_CANCEL)) {
                 return false;
             }
-            mDesktopTasksController.ifPresent(c -> {
-                final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
-                c.toggleDesktopTaskSize(decoration.mTaskInfo);
-            });
+            final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+            mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
             return true;
         }
     }
@@ -839,20 +838,29 @@
                     return;
                 }
                 if (mTransitionDragActive) {
+                    final DesktopModeVisualIndicator.IndicatorType indicatorType =
+                            mDesktopTasksController.updateVisualIndicator(relevantDecor.mTaskInfo,
+                                    relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY());
                     mTransitionDragActive = false;
-                    final int statusBarHeight = getStatusBarHeight(
-                            relevantDecor.mTaskInfo.displayId);
-                    if (ev.getRawY() > 2 * statusBarHeight) {
+                    if (indicatorType == TO_DESKTOP_INDICATOR
+                            || indicatorType == TO_SPLIT_LEFT_INDICATOR
+                            || indicatorType == TO_SPLIT_RIGHT_INDICATOR) {
                         if (DesktopModeStatus.isEnabled()) {
                             animateToDesktop(relevantDecor, ev);
                         }
                         mMoveToDesktopAnimator = null;
                         return;
                     } else if (mMoveToDesktopAnimator != null) {
-                        mDesktopTasksController.ifPresent(
-                                c -> c.cancelDragToDesktop(relevantDecor.mTaskInfo));
+                        mDesktopTasksController.onDragPositioningEndThroughStatusBar(
+                                new PointF(ev.getRawX(), ev.getRawY()),
+                                relevantDecor.mTaskInfo,
+                                calculateFreeformBounds(ev.getDisplayId(), DRAG_FREEFORM_SCALE));
                         mMoveToDesktopAnimator = null;
                         return;
+                    } else {
+                        // In cases where we create an indicator but do not start the
+                        // move-to-desktop animation, we need to dismiss it.
+                        mDesktopTasksController.releaseVisualIndicator();
                     }
                 }
                 relevantDecor.checkClickEvent(ev);
@@ -864,20 +872,17 @@
                     return;
                 }
                 if (mTransitionDragActive) {
-                    mDesktopTasksController.ifPresent(
-                            c -> c.updateVisualIndicator(
+                    final DesktopModeVisualIndicator.IndicatorType indicatorType =
+                            mDesktopTasksController.updateVisualIndicator(
                                     relevantDecor.mTaskInfo,
-                                    relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY()));
-                    final int statusBarHeight = getStatusBarHeight(
-                            relevantDecor.mTaskInfo.displayId);
-                    if (ev.getRawY() > statusBarHeight) {
+                                    relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY());
+                    if (indicatorType != TO_FULLSCREEN_INDICATOR) {
                         if (mMoveToDesktopAnimator == null) {
                             mMoveToDesktopAnimator = new MoveToDesktopAnimator(
                                     mContext, mDragToDesktopAnimationStartBounds,
                                     relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
-                            mDesktopTasksController.ifPresent(
-                                    c -> c.startDragToDesktop(relevantDecor.mTaskInfo,
-                                            mMoveToDesktopAnimator));
+                            mDesktopTasksController.startDragToDesktop(relevantDecor.mTaskInfo,
+                                    mMoveToDesktopAnimator);
                         }
                     }
                     if (mMoveToDesktopAnimator != null) {
@@ -923,6 +928,8 @@
      * Animates a window to the center, grows to freeform size, and transitions to Desktop Mode.
      * @param relevantDecor the window decor of the task to be animated
      * @param ev the motion event that triggers the animation
+     * TODO(b/315527000): This animation needs to be adjusted to allow snap left/right cases.
+     *  Currently fullscreen -> split snap still animates to center screen before readjusting.
      */
     private void centerAndMoveToDesktopWithAnimation(DesktopModeWindowDecoration relevantDecor,
             MotionEvent ev) {
@@ -946,13 +953,12 @@
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                mDesktopTasksController.ifPresent(
-                        c -> {
-                            c.onDragPositioningEndThroughStatusBar(relevantDecor.mTaskInfo,
-                                    calculateFreeformBounds(ev.getDisplayId(),
-                                            DesktopTasksController
-                                                    .DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
-                        });
+                mDesktopTasksController.onDragPositioningEndThroughStatusBar(
+                        new PointF(ev.getRawX(), ev.getRawY()),
+                        relevantDecor.mTaskInfo,
+                        calculateFreeformBounds(ev.getDisplayId(),
+                                DesktopTasksController
+                                        .DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
             }
         });
         animator.start();
@@ -1082,7 +1088,7 @@
 
         final DragPositioningCallback dragPositioningCallback;
         final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
-                R.dimen.desktop_mode_transition_area_height);
+                R.dimen.desktop_mode_fullscreen_from_desktop_height);
         if (!DesktopModeStatus.isVeiledResizeEnabled()) {
             dragPositioningCallback =  new FluidResizeTaskPositioner(
                     mTaskOrganizer, mTransitions, windowDecoration, mDisplayController,
@@ -1181,12 +1187,12 @@
 
         @Override
         public void onExclusionRegionChanged(int taskId, Region region) {
-            mDesktopTasksController.ifPresent(d -> d.onExclusionRegionChanged(taskId, region));
+            mDesktopTasksController.onExclusionRegionChanged(taskId, region);
         }
 
         @Override
         public void onExclusionRegionDismissed(int taskId) {
-            mDesktopTasksController.ifPresent(d -> d.removeExclusionRegionForTask(taskId));
+            mDesktopTasksController.removeExclusionRegionForTask(taskId);
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index f8ce4ee..9703dce 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -56,18 +56,16 @@
             context, taskSurface, taskDisplayAreaOrganizer)
         whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
         whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
+        whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS)
     }
 
     @Test
     fun testFullscreenRegionCalculation() {
         val transitionHeight = context.resources.getDimensionPixelSize(
-            R.dimen.desktop_mode_transition_area_height)
+            R.dimen.desktop_mode_fullscreen_from_desktop_height)
         val fromFreeformWidth = mContext.resources.getDimensionPixelSize(
             R.dimen.desktop_mode_fullscreen_from_desktop_width
         )
-        val fromFreeformHeight = mContext.resources.getDimensionPixelSize(
-            R.dimen.desktop_mode_fullscreen_from_desktop_height
-        )
         var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
             WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
         assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
@@ -77,7 +75,7 @@
             DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2,
             -50,
             DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2,
-            fromFreeformHeight))
+            2 * STABLE_INSETS.top))
         testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
             WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT)
         assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
@@ -135,5 +133,12 @@
         private const val TRANSITION_AREA_WIDTH = 32
         private const val CAPTION_HEIGHT = 50
         private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
+        private const val NAVBAR_HEIGHT = 50
+        private val STABLE_INSETS = Rect(
+            DISPLAY_BOUNDS.left,
+            DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
+            DISPLAY_BOUNDS.right,
+            DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
+        )
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 917fd71..9bb5482 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -29,6 +29,7 @@
 import android.os.Handler
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import android.util.SparseArray
 import android.view.Choreographer
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.IWindowManager
@@ -72,6 +73,8 @@
 import org.mockito.kotlin.whenever
 import java.util.Optional
 import java.util.function.Supplier
+import org.mockito.Mockito
+import org.mockito.kotlin.spy
 
 
 /** Tests of [DesktopModeWindowDecorViewModel]  */
@@ -102,6 +105,7 @@
     private val transactionFactory = Supplier<SurfaceControl.Transaction> {
         SurfaceControl.Transaction()
     }
+    private val windowDecorByTaskIdSpy = spy(SparseArray<DesktopModeWindowDecoration>())
 
     private lateinit var shellInit: ShellInit
     private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
@@ -110,6 +114,7 @@
     @Before
     fun setUp() {
         shellInit = ShellInit(mockShellExecutor)
+        windowDecorByTaskIdSpy.clear()
         desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
                 mContext,
                 mockShellExecutor,
@@ -128,7 +133,8 @@
                 mockDesktopModeWindowDecorFactory,
                 mockInputMonitorFactory,
                 transactionFactory,
-                mockRootTaskDisplayAreaOrganizer
+                mockRootTaskDisplayAreaOrganizer,
+            windowDecorByTaskIdSpy
         )
 
         whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -332,6 +338,19 @@
         verify(decoration, times(1)).relayout(task)
     }
 
+    @Test
+    fun testDestroyWindowDecoration_closesBeforeCleanup() {
+        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+        val decoration = setUpMockDecorationForTask(task)
+        val inOrder = Mockito.inOrder(decoration, windowDecorByTaskIdSpy)
+
+        onTaskOpening(task)
+        desktopModeWindowDecorViewModel.destroyWindowDecoration(task)
+
+        inOrder.verify(decoration).close()
+        inOrder.verify(windowDecorByTaskIdSpy).remove(task.taskId)
+    }
+
     private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
         desktopModeWindowDecorViewModel.onTaskOpening(
                 task,
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
index b435bb8..892eabf 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
@@ -138,6 +138,7 @@
                     entryGroupId = credentialEntry.entryGroupId.toString(),
                     isDefaultIconPreferredAsSingleProvider =
                             credentialEntry.isDefaultIconPreferredAsSingleProvider,
+                    affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
                 )
                 )
             }
@@ -165,6 +166,7 @@
                     entryGroupId = credentialEntry.entryGroupId.toString(),
                     isDefaultIconPreferredAsSingleProvider =
                             credentialEntry.isDefaultIconPreferredAsSingleProvider,
+                    affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
                 )
                 )
             }
@@ -191,6 +193,7 @@
                     entryGroupId = credentialEntry.entryGroupId.toString(),
                     isDefaultIconPreferredAsSingleProvider =
                             credentialEntry.isDefaultIconPreferredAsSingleProvider,
+                    affiliatedDomain = credentialEntry.affiliatedDomain?.toString(),
                 )
                 )
             }
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt
index ac42b60..a657e97 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/get/CredentialEntryInfo.kt
@@ -48,6 +48,7 @@
     val entryGroupId: String, // Used for deduplication, and displayed as the grouping title
                               // "For <value-of-entryGroupId>" on the more-option screen.
     val isDefaultIconPreferredAsSingleProvider: Boolean,
+    val affiliatedDomain: String?,
 ) : EntryInfo(
     providerId,
     entryKey,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 8ff17e0..965ee86 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -78,6 +78,8 @@
     isLockedAuthEntry: Boolean = false,
     enforceOneLine: Boolean = false,
     onTextLayout: (TextLayoutResult) -> Unit = {},
+    /** Get flow only, if present, where be drawn as a line above the headline. */
+    affiliatedDomainText: String? = null,
 ) {
     val iconPadding = Modifier.wrapContentSize().padding(
         // Horizontal padding should be 16dp, but the suggestion chip itself
@@ -102,6 +104,13 @@
             ) {
                 // Apply weight so that the trailing icon can always show.
                 Column(modifier = Modifier.wrapContentHeight().fillMaxWidth().weight(1f)) {
+                    if (!affiliatedDomainText.isNullOrBlank()) {
+                        BodySmallText(
+                            text = affiliatedDomainText,
+                            enforceOneLine = enforceOneLine,
+                            onTextLayout = onTextLayout,
+                        )
+                    }
                     SmallTitleText(
                         text = entryHeadlineText,
                         enforceOneLine = enforceOneLine,
@@ -143,14 +152,14 @@
                                 },
                             )
                         }
-                    } else if (entrySecondLineText != null) {
+                    } else if (!entrySecondLineText.isNullOrBlank()) {
                         BodySmallText(
                             text = entrySecondLineText,
                             enforceOneLine = enforceOneLine,
                             onTextLayout = onTextLayout,
                         )
                     }
-                    if (entryThirdLineText != null) {
+                    if (!entryThirdLineText.isNullOrBlank()) {
                         BodySmallText(
                             text = entryThirdLineText,
                             enforceOneLine = enforceOneLine,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 1fef522..bc0ea02 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -376,6 +376,7 @@
 }
 
 internal const val MAX_ENTRY_FOR_PRIMARY_PAGE = 4
+
 /** Draws the primary credential selection page, used starting from android V. */
 @Composable
 fun PrimarySelectionCardVImpl(
@@ -785,16 +786,16 @@
         else if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24)
         else null,
         entryHeadlineText = username,
-        entrySecondLineText =
+        entrySecondLineText = displayName,
+        entryThirdLineText =
         (if (hasSingleEntry != null && hasSingleEntry)
             if (credentialEntryInfo.credentialType == CredentialType.PASSKEY ||
                     credentialEntryInfo.credentialType == CredentialType.PASSWORD)
-                listOf(displayName)
+                emptyList()
             // Still show the type display name for all non-password/passkey types since it won't be
             // mentioned in the bottom sheet heading.
-            else listOf(displayName, credentialEntryInfo.credentialTypeDisplayName)
+            else listOf(credentialEntryInfo.credentialTypeDisplayName)
         else listOf(
-                displayName,
                 credentialEntryInfo.credentialTypeDisplayName,
                 credentialEntryInfo.providerDisplayName
         )).filterNot(TextUtils::isEmpty).let { itemsToDisplay ->
@@ -805,6 +806,7 @@
         },
         enforceOneLine = enforceOneLine,
         onTextLayout = onTextLayout,
+        affiliatedDomainText = credentialEntryInfo.affiliatedDomain,
     )
 }
 
diff --git a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
index 0820d26..28d83ee 100644
--- a/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
+++ b/packages/CredentialManager/tests/robotests/screenshot/src/com/android/credentialmanager/GetCredScreenshotTest.kt
@@ -151,6 +151,7 @@
                                 entryGroupId = "username",
                                 isDefaultIconPreferredAsSingleProvider = false,
                                 rawCredentialType = "unknown-type",
+                                affiliatedDomain = null,
                         )
                 ),
                 authenticationEntryList = emptyList(),
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index 634e067..cf2f85e 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -20,7 +20,6 @@
 
 import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
 
-import android.Manifest;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -28,10 +27,10 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
-import android.content.pm.Flags;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.res.AssetFileDescriptor;
+import android.Manifest;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -201,7 +200,7 @@
         params.setPermissionState(Manifest.permission.USE_FULL_SCREEN_INTENT,
                 PackageInstaller.SessionParams.PERMISSION_STATE_DENIED);
 
-        if (pfd != null && Flags.readInstallInfo()) {
+        if (pfd != null) {
             try {
                 final PackageInstaller.InstallInfo result = installer.readInstallInfo(pfd,
                         debugPathName, 0);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index e95a8e6..45bfe54 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -31,7 +31,6 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.Flags;
 import android.content.pm.InstallSourceInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
@@ -400,10 +399,7 @@
             final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
                     -1 /* defaultValue */);
             final SessionInfo info = mInstaller.getSessionInfo(sessionId);
-            String resolvedPath = null;
-            if (info != null && Flags.getResolvedApkPath()) {
-                resolvedPath = info.getResolvedBaseApkPath();
-            }
+            String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null;
             if (info == null || !info.isSealed() || resolvedPath == null) {
                 Log.w(TAG, "Session " + sessionId + " in funky state; ignoring");
                 finish();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index 22caabd..aeabbd5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -25,7 +25,6 @@
 import android.content.Context
 import android.content.Intent
 import android.content.pm.ApplicationInfo
-import android.content.pm.Flags
 import android.content.pm.PackageInfo
 import android.content.pm.PackageInstaller
 import android.content.pm.PackageInstaller.SessionInfo
@@ -363,7 +362,7 @@
         params.setPermissionState(
             Manifest.permission.USE_FULL_SCREEN_INTENT, SessionParams.PERMISSION_STATE_DENIED
         )
-        if (pfd != null && Flags.readInstallInfo()) {
+        if (pfd != null) {
             try {
                 val installInfo = packageInstaller.readInstallInfo(pfd, debugPathName, 0)
                 params.setAppPackageName(installInfo.packageName)
@@ -426,8 +425,7 @@
 
         if (PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action) {
             val info = packageInstaller.getSessionInfo(sessionId)
-            val resolvedPath =
-                    if (Flags.getResolvedApkPath()) info?.resolvedBaseApkPath else null
+            val resolvedPath = info?.resolvedBaseApkPath
             if (info == null || !info.isSealed || resolvedPath == null) {
                 Log.w(LOG_TAG, "Session $sessionId in funky state; ignoring")
                 return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
diff --git a/packages/SettingsLib/DataStore/tests/Android.bp b/packages/SettingsLib/DataStore/tests/Android.bp
new file mode 100644
index 0000000..8770dfa
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/Android.bp
@@ -0,0 +1,24 @@
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_app {
+    name: "SettingsLibDataStoreShell",
+    platform_apis: true,
+}
+
+android_robolectric_test {
+    name: "SettingsLibDataStoreTest",
+    srcs: ["src/**/*"],
+    static_libs: [
+        "SettingsLibDataStore",
+        "androidx.test.ext.junit",
+        "guava",
+        "mockito-robolectric-prebuilt", // mockito deps order matters!
+        "mockito-kotlin2",
+    ],
+    java_resource_dirs: ["config"],
+    instrumentation_for: "SettingsLibDataStoreShell",
+    coverage_libs: ["SettingsLibDataStore"],
+    upstream: true,
+}
diff --git a/packages/SettingsLib/DataStore/tests/AndroidManifest.xml b/packages/SettingsLib/DataStore/tests/AndroidManifest.xml
new file mode 100644
index 0000000..ffc24e4
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.settingslib.datastore.test">
+
+    <application android:debuggable="true" />
+</manifest>
diff --git a/packages/SettingsLib/DataStore/tests/config/robolectric.properties b/packages/SettingsLib/DataStore/tests/config/robolectric.properties
new file mode 100644
index 0000000..fab7251
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/config/robolectric.properties
@@ -0,0 +1 @@
+sdk=NEWEST_SDK
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt
new file mode 100644
index 0000000..bb791dc
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicInteger
+import org.junit.Assert.assertThrows
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+
+@RunWith(AndroidJUnit4::class)
+class ObserverTest {
+    @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    @Mock private lateinit var observer1: Observer
+
+    @Mock private lateinit var observer2: Observer
+
+    @Mock private lateinit var executor: Executor
+
+    private val observable = DataObservable()
+
+    @Test
+    fun addObserver_sameExecutor() {
+        observable.addObserver(observer1, executor)
+        observable.addObserver(observer1, executor)
+    }
+
+    @Test
+    fun addObserver_differentExecutor() {
+        observable.addObserver(observer1, executor)
+        assertThrows(IllegalStateException::class.java) {
+            observable.addObserver(observer1, MoreExecutors.directExecutor())
+        }
+    }
+
+    @Test
+    fun addObserver_weaklyReferenced() {
+        val counter = AtomicInteger()
+        var observer: Observer? = Observer { counter.incrementAndGet() }
+        observable.addObserver(observer!!, MoreExecutors.directExecutor())
+
+        observable.notifyChange(ChangeReason.UPDATE)
+        assertThat(counter.get()).isEqualTo(1)
+
+        // trigger GC, the observer callback should not be invoked
+        @Suppress("unused")
+        observer = null
+        System.gc()
+        System.runFinalization()
+
+        observable.notifyChange(ChangeReason.UPDATE)
+        assertThat(counter.get()).isEqualTo(1)
+    }
+
+    @Test
+    fun addObserver_notifyObservers_removeObserver() {
+        observable.addObserver(observer1, MoreExecutors.directExecutor())
+        observable.addObserver(observer2, executor)
+
+        observable.notifyChange(ChangeReason.DELETE)
+
+        verify(observer1).onChanged(ChangeReason.DELETE)
+        verify(observer2, never()).onChanged(any())
+        verify(executor).execute(any())
+
+        reset(observer1, executor)
+        observable.removeObserver(observer2)
+
+        observable.notifyChange(ChangeReason.UPDATE)
+        verify(observer1).onChanged(ChangeReason.UPDATE)
+        verify(executor, never()).execute(any())
+    }
+
+    @Test
+    fun notifyChange_addObserverWithinCallback() {
+        // ConcurrentModificationException is raised if it is not implemented correctly
+        observable.addObserver(
+            { observable.addObserver(observer1, executor) },
+            MoreExecutors.directExecutor()
+        )
+        observable.notifyChange(ChangeReason.UPDATE)
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index bdb5871..5f026c4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -74,6 +74,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -84,6 +85,7 @@
 
     private static final String TAG = "InfoMediaManager";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
 
     /** Checked exception that signals the specified package is not present in the system. */
     public static class PackageNotAvailableException extends Exception {
@@ -227,6 +229,16 @@
         Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference, mPreferenceItemMap);
     }
 
+    protected final MediaDevice findMediaDevice(@NonNull String id) {
+        for (MediaDevice mediaDevice : mMediaDevices) {
+            if (mediaDevice.getId().equals(id)) {
+                return mediaDevice;
+            }
+        }
+        Log.e(TAG, "findMediaDevice() can't find device with id: " + id);
+        return null;
+    }
+
     /**
      * Get current device that played media.
      * @return MediaDevice
@@ -433,7 +445,7 @@
 
     protected final synchronized void refreshDevices() {
         rebuildDeviceList();
-        dispatchDeviceListAdded();
+        dispatchDeviceListAdded(mMediaDevices);
     }
 
     // MediaRoute2Info.getType was made public on API 34, but exists since API 30.
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
index 8bebd6e..d562c8a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
@@ -15,9 +15,9 @@
  */
 package com.android.settingslib.media;
 
+import android.annotation.NonNull;
 import android.app.Notification;
 import android.content.Context;
-import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -29,10 +29,7 @@
  */
 public abstract class MediaManager {
 
-    private static final String TAG = "MediaManager";
-
     protected final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
-    protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
 
     protected Context mContext;
     protected Notification mNotification;
@@ -54,19 +51,9 @@
         }
     }
 
-    protected MediaDevice findMediaDevice(String id) {
-        for (MediaDevice mediaDevice : mMediaDevices) {
-            if (mediaDevice.getId().equals(id)) {
-                return mediaDevice;
-            }
-        }
-        Log.e(TAG, "findMediaDevice() can't found device");
-        return null;
-    }
-
-    protected void dispatchDeviceListAdded() {
+    protected void dispatchDeviceListAdded(@NonNull List<MediaDevice> devices) {
         for (MediaDeviceCallback callback : getCallbacks()) {
-            callback.onDeviceListAdded(new ArrayList<>(mMediaDevices));
+            callback.onDeviceListAdded(new ArrayList<>(devices));
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
index 65b73ca..0117ece 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
@@ -46,7 +46,7 @@
     /**
      * Wrapper the [.getInternetIconResource] for testing compatibility.
      */
-    class InternetIconInjector(protected val context: Context) {
+    open class InternetIconInjector(protected val context: Context) {
         /**
          * Returns the Internet icon for a given RSSI level.
          *
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
index 46e724d..c3237f0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.media;
 
-import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.verify;
@@ -32,6 +31,8 @@
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
 
+import java.util.Collections;
+
 @RunWith(RobolectricTestRunner.class)
 public class MediaManagerTest {
 
@@ -59,7 +60,7 @@
     public void dispatchDeviceListAdded_registerCallback_shouldDispatchCallback() {
         mMediaManager.registerCallback(mCallback);
 
-        mMediaManager.dispatchDeviceListAdded();
+        mMediaManager.dispatchDeviceListAdded(Collections.emptyList());
 
         verify(mCallback).onDeviceListAdded(any());
     }
@@ -68,9 +69,9 @@
     public void dispatchDeviceListRemoved_registerCallback_shouldDispatchCallback() {
         mMediaManager.registerCallback(mCallback);
 
-        mMediaManager.dispatchDeviceListRemoved(mMediaManager.mMediaDevices);
+        mMediaManager.dispatchDeviceListRemoved(Collections.emptyList());
 
-        verify(mCallback).onDeviceListRemoved(mMediaManager.mMediaDevices);
+        verify(mCallback).onDeviceListRemoved(Collections.emptyList());
     }
 
     @Test
@@ -83,24 +84,6 @@
     }
 
     @Test
-    public void findMediaDevice_idExist_shouldReturnMediaDevice() {
-        mMediaManager.mMediaDevices.add(mDevice);
-
-        final MediaDevice device = mMediaManager.findMediaDevice(TEST_ID);
-
-        assertThat(device.getId()).isEqualTo(mDevice.getId());
-    }
-
-    @Test
-    public void findMediaDevice_idNotExist_shouldReturnNull() {
-        mMediaManager.mMediaDevices.add(mDevice);
-
-        final MediaDevice device = mMediaManager.findMediaDevice("123");
-
-        assertThat(device).isNull();
-    }
-
-    @Test
     public void dispatchOnRequestFailed_registerCallback_shouldDispatchCallback() {
         mMediaManager.registerCallback(mCallback);
 
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 2fa1c6e..a490b6f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -154,6 +154,7 @@
         Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT,
         Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
         Settings.Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED,
+        Settings.Secure.EMERGENCY_THERMAL_ALERT_DISABLED,
         Settings.Secure.HUSH_GESTURE_USED,
         Settings.Secure.IN_CALL_NOTIFICATION_ENABLED,
         Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 2535fdb..4cdf98cb 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -235,6 +235,7 @@
         VALIDATORS.put(Secure.MANUAL_RINGER_TOGGLE_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.LOW_POWER_WARNING_ACKNOWLEDGED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.EMERGENCY_THERMAL_ALERT_DISABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.IN_CALL_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index 6ff36d4..ad3eb92 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -20,8 +20,6 @@
 import static android.provider.Settings.Config.SYNC_DISABLED_MODE_PERSISTENT;
 import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT;
 
-import static com.android.providers.settings.Flags.supportOverrides;
-
 import android.aconfig.Aconfig.parsed_flag;
 import android.aconfig.Aconfig.parsed_flags;
 import android.annotation.SuppressLint;
@@ -269,9 +267,9 @@
                 verb = CommandVerb.GET;
             } else if ("put".equalsIgnoreCase(cmd)) {
                 verb = CommandVerb.PUT;
-            } else if (supportOverrides() && "override".equalsIgnoreCase(cmd)) {
+            } else if ("override".equalsIgnoreCase(cmd)) {
                 verb = CommandVerb.OVERRIDE;
-            } else if (supportOverrides() && "clear_override".equalsIgnoreCase(cmd)) {
+            } else if ("clear_override".equalsIgnoreCase(cmd)) {
                 verb = CommandVerb.CLEAR_OVERRIDE;
             } else if ("delete".equalsIgnoreCase(cmd)) {
                 verb = CommandVerb.DELETE;
@@ -285,7 +283,7 @@
                 if (peekNextArg() == null) {
                     isValid = true;
                 }
-            } else if (supportOverrides() && "list_local_overrides".equalsIgnoreCase(cmd)) {
+            } else if ("list_local_overrides".equalsIgnoreCase(cmd)) {
                 verb = CommandVerb.LIST_LOCAL_OVERRIDES;
                 if (peekNextArg() == null) {
                     isValid = true;
@@ -427,14 +425,10 @@
                     DeviceConfig.setProperty(namespace, key, value, makeDefault);
                     break;
                 case OVERRIDE:
-                    if (supportOverrides()) {
-                        DeviceConfig.setLocalOverride(namespace, key, value);
-                    }
+                    DeviceConfig.setLocalOverride(namespace, key, value);
                     break;
                 case CLEAR_OVERRIDE:
-                    if (supportOverrides()) {
-                        DeviceConfig.clearLocalOverride(namespace, key);
-                    }
+                    DeviceConfig.clearLocalOverride(namespace, key);
                     break;
                 case DELETE:
                     pout.println(delete(iprovider, namespace, key)
@@ -452,19 +446,15 @@
                         }
                     } else {
                         for (String line : listAll(iprovider)) {
-                            if (supportOverrides()) {
-                                boolean isPrivate = false;
-                                for (String privateNamespace : PRIVATE_NAMESPACES) {
-                                    if (line.startsWith(privateNamespace)) {
-                                        isPrivate = true;
-                                        break;
-                                    }
+                            boolean isPrivate = false;
+                            for (String privateNamespace : PRIVATE_NAMESPACES) {
+                                if (line.startsWith(privateNamespace)) {
+                                    isPrivate = true;
+                                    break;
                                 }
+                            }
 
-                                if (!isPrivate) {
-                                    pout.println(line);
-                                }
-                            } else {
+                            if (!isPrivate) {
                                 pout.println(line);
                             }
                         }
@@ -495,18 +485,16 @@
                     }
                     break;
                 case LIST_LOCAL_OVERRIDES:
-                    if (supportOverrides()) {
-                        Map<String, Map<String, String>> underlyingValues =
-                                DeviceConfig.getUnderlyingValuesForOverriddenFlags();
-                        for (String overrideNamespace : underlyingValues.keySet()) {
-                            Map<String, String> flagToValue =
-                                    underlyingValues.get(overrideNamespace);
-                            for (String flag : flagToValue.keySet()) {
-                                String flagText = overrideNamespace + "/" + flag;
-                                String valueText =
-                                        DeviceConfig.getProperty(overrideNamespace, flag);
-                                pout.println(flagText + "=" + valueText);
-                            }
+                    Map<String, Map<String, String>> underlyingValues =
+                            DeviceConfig.getUnderlyingValuesForOverriddenFlags();
+                    for (String overrideNamespace : underlyingValues.keySet()) {
+                        Map<String, String> flagToValue =
+                                underlyingValues.get(overrideNamespace);
+                        for (String flag : flagToValue.keySet()) {
+                            String flagText = overrideNamespace + "/" + flag;
+                            String valueText =
+                                    DeviceConfig.getProperty(overrideNamespace, flag);
+                            pout.println(flagText + "=" + valueText);
                         }
                     }
                     break;
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index 085fc29..88181e7 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -56,10 +56,12 @@
         implements View.OnTouchListener {
 
     public static final String PACKAGE_NAME = AccessibilityMenuService.class.getPackageName();
+    public static final String PACKAGE_TESTS = ".tests";
     public static final String INTENT_TOGGLE_MENU = ".toggle_menu";
     public static final String INTENT_HIDE_MENU = ".hide_menu";
     public static final String INTENT_GLOBAL_ACTION = ".global_action";
     public static final String INTENT_GLOBAL_ACTION_EXTRA = "GLOBAL_ACTION";
+    public static final String INTENT_OPEN_BLOCKED = "OPEN_BLOCKED";
 
     private static final String TAG = "A11yMenuService";
     private static final long BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE = 100L;
@@ -192,7 +194,7 @@
 
         IntentFilter hideMenuFilter = new IntentFilter();
         hideMenuFilter.addAction(Intent.ACTION_SCREEN_OFF);
-        hideMenuFilter.addAction(PACKAGE_NAME + INTENT_HIDE_MENU);
+        hideMenuFilter.addAction(INTENT_HIDE_MENU);
 
         // Including WRITE_SECURE_SETTINGS enforces that we only listen to apps
         // with the restricted WRITE_SECURE_SETTINGS permission who broadcast this intent.
@@ -200,7 +202,7 @@
                 Manifest.permission.WRITE_SECURE_SETTINGS, null,
                 Context.RECEIVER_EXPORTED);
         registerReceiver(mToggleMenuReceiver,
-                new IntentFilter(PACKAGE_NAME + INTENT_TOGGLE_MENU),
+                new IntentFilter(INTENT_TOGGLE_MENU),
                 Manifest.permission.WRITE_SECURE_SETTINGS, null,
                 Context.RECEIVER_EXPORTED);
 
@@ -245,8 +247,9 @@
      * @return {@code true} if successful, {@code false} otherwise.
      */
     private boolean performGlobalActionInternal(int globalAction) {
-        Intent intent = new Intent(PACKAGE_NAME + INTENT_GLOBAL_ACTION);
+        Intent intent = new Intent(INTENT_GLOBAL_ACTION);
         intent.putExtra(INTENT_GLOBAL_ACTION_EXTRA, globalAction);
+        intent.setPackage(PACKAGE_NAME + PACKAGE_TESTS);
         sendBroadcast(intent);
         Log.i("A11yMenuService", "Broadcasting global action " + globalAction);
         return performGlobalAction(globalAction);
@@ -410,9 +413,16 @@
 
     private void toggleVisibility() {
         boolean locked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
-        if (!locked && SystemClock.uptimeMillis() - mLastTimeTouchedOutside
-                        > BUTTON_CLICK_TIMEOUT) {
-            mA11yMenuLayout.toggleVisibility();
+        if (!locked) {
+            if (SystemClock.uptimeMillis() - mLastTimeTouchedOutside
+                    > BUTTON_CLICK_TIMEOUT) {
+                mA11yMenuLayout.toggleVisibility();
+            }
+        } else {
+            // Broadcast for testing.
+            Intent intent = new Intent(INTENT_OPEN_BLOCKED);
+            intent.setPackage(PACKAGE_NAME + PACKAGE_TESTS);
+            sendBroadcast(intent);
         }
     }
 }
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 72c1092..6546b87 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -23,6 +23,7 @@
 import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_RECENTS;
 import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT;
 
+import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_OPEN_BLOCKED;
 import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION;
 import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION_EXTRA;
 import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_HIDE_MENU;
@@ -65,6 +66,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
 @RunWith(AndroidJUnit4.class)
@@ -75,12 +77,11 @@
     private static final int TIMEOUT_SERVICE_STATUS_CHANGE_S = 5;
     private static final int TIMEOUT_UI_CHANGE_S = 5;
     private static final int NO_GLOBAL_ACTION = -1;
-    private static final String INPUT_KEYEVENT_KEYCODE_BACK = "input keyevent KEYCODE_BACK";
-    private static final String TEST_PIN = "1234";
 
     private static Instrumentation sInstrumentation;
     private static UiAutomation sUiAutomation;
-    private static AtomicInteger sLastGlobalAction;
+    private static final AtomicInteger sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
+    private static final AtomicBoolean sOpenBlocked = new AtomicBoolean(false);
 
     private static AccessibilityManager sAccessibilityManager;
     private static PowerManager sPowerManager;
@@ -122,8 +123,6 @@
                 () -> sAccessibilityManager.getEnabledAccessibilityServiceList(
                         AccessibilityServiceInfo.FEEDBACK_ALL_MASK).stream().filter(
                                 info -> info.getId().contains(serviceName)).count() == 1);
-
-        sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
         context.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
@@ -131,20 +130,28 @@
                 sLastGlobalAction.set(
                         intent.getIntExtra(INTENT_GLOBAL_ACTION_EXTRA, NO_GLOBAL_ACTION));
             }},
-                new IntentFilter(PACKAGE_NAME + INTENT_GLOBAL_ACTION),
+                new IntentFilter(INTENT_GLOBAL_ACTION),
+                null, null, Context.RECEIVER_EXPORTED);
+
+        context.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                Log.i(TAG, "Received notification that menu cannot be opened.");
+                sOpenBlocked.set(true);
+            }},
+                new IntentFilter(INTENT_OPEN_BLOCKED),
                 null, null, Context.RECEIVER_EXPORTED);
     }
 
     @AfterClass
-    public static void classTeardown() throws Throwable {
-        clearPin();
+    public static void classTeardown() {
         Settings.Secure.putString(sInstrumentation.getTargetContext().getContentResolver(),
                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
     }
 
     @Before
     public void setup() throws Throwable {
-        clearPin();
+        sOpenBlocked.set(false);
         wakeUpScreen();
         sUiAutomation.executeShellCommand("input keyevent KEYCODE_MENU");
         openMenu();
@@ -154,20 +161,8 @@
     public void tearDown() throws Throwable {
         closeMenu();
         sLastGlobalAction.set(NO_GLOBAL_ACTION);
-    }
-
-    private static void clearPin() throws Throwable {
-        sUiAutomation.executeShellCommand("locksettings clear --old " + TEST_PIN);
-        TestUtils.waitUntil("Device did not register as unlocked & insecure.",
-                TIMEOUT_SERVICE_STATUS_CHANGE_S,
-                () -> !sKeyguardManager.isDeviceSecure());
-    }
-
-    private static void setPin() throws Throwable {
-        sUiAutomation.executeShellCommand("locksettings set-pin " + TEST_PIN);
-        TestUtils.waitUntil("Device did not recognize as locked & secure.",
-                TIMEOUT_SERVICE_STATUS_CHANGE_S,
-                () -> sKeyguardManager.isDeviceSecure());
+        // dismisses screenshot popup if present.
+        sUiAutomation.executeShellCommand("input keyevent KEYCODE_BACK");
     }
 
     private static boolean isMenuVisible() {
@@ -184,7 +179,6 @@
 
     private static void closeScreen() throws Throwable {
         Display display = sDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
-        setPin();
         sUiAutomation.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN);
         TestUtils.waitUntil("Screen did not close.",
                 TIMEOUT_UI_CHANGE_S,
@@ -194,12 +188,20 @@
     }
 
     private static void openMenu() throws Throwable {
-        Intent intent = new Intent(PACKAGE_NAME + INTENT_TOGGLE_MENU);
+        openMenu(false);
+    }
+
+    private static void openMenu(boolean abandonOnBlock) throws Throwable {
+        Intent intent = new Intent(INTENT_TOGGLE_MENU);
+        intent.setPackage(PACKAGE_NAME);
         sInstrumentation.getContext().sendBroadcast(intent);
 
         TestUtils.waitUntil("Timed out before menu could appear.",
                 TIMEOUT_UI_CHANGE_S,
                 () -> {
+                    if (sOpenBlocked.get() && abandonOnBlock) {
+                        throw new IllegalStateException();
+                    }
                     if (isMenuVisible()) {
                         return true;
                     } else {
@@ -213,7 +215,8 @@
         if (!isMenuVisible()) {
             return;
         }
-        Intent intent = new Intent(PACKAGE_NAME + INTENT_HIDE_MENU);
+        Intent intent = new Intent(INTENT_HIDE_MENU);
+        intent.setPackage(PACKAGE_NAME);
         sInstrumentation.getContext().sendBroadcast(intent);
         TestUtils.waitUntil("Timed out before menu could close.",
                 TIMEOUT_UI_CHANGE_S, () -> !isMenuVisible());
@@ -444,13 +447,13 @@
         closeScreen();
         wakeUpScreen();
 
-        boolean timedOut = false;
+        boolean blocked = false;
         try {
-            openMenu();
-        } catch (AssertionError e) {
+            openMenu(true);
+        } catch (IllegalStateException e) {
             // Expected
-            timedOut = true;
+            blocked = true;
         }
-        assertThat(timedOut).isTrue();
+        assertThat(blocked).isTrue();
     }
 }
diff --git a/packages/SystemUI/aconfig/predictive_back.aconfig b/packages/SystemUI/aconfig/predictive_back.aconfig
index d0e6b28..7bbe82c 100644
--- a/packages/SystemUI/aconfig/predictive_back.aconfig
+++ b/packages/SystemUI/aconfig/predictive_back.aconfig
@@ -4,26 +4,26 @@
     name: "predictive_back_sysui"
     namespace: "systemui"
     description: "Predictive Back Dispatching for SysUI"
-    bug: "309545085"
+    bug: "327737297"
 }
 
 flag {
     name: "predictive_back_animate_shade"
     namespace: "systemui"
     description: "Enable Shade Animations"
-    bug: "309545085"
+    bug: "327732946"
 }
 
 flag {
     name: "predictive_back_animate_bouncer"
     namespace: "systemui"
     description: "Enable Predictive Back Animation in Bouncer"
-    bug: "309545085"
+    bug: "327733487"
 }
 
 flag {
     name: "predictive_back_animate_dialogs"
     namespace: "systemui"
     description: "Enable Predictive Back Animation for SysUI dialogs"
-    bug: "309545085"
+    bug: "327721544"
 }
\ No newline at end of file
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 078da1c86..515c816 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -31,8 +31,8 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
@@ -93,6 +93,7 @@
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.testTagsAsResourceId
@@ -118,6 +119,7 @@
 import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
+import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.res.R
 import kotlinx.coroutines.launch
@@ -197,26 +199,37 @@
                     }
                 },
     ) {
-        CommunalHubLazyGrid(
-            communalContent = communalContent,
-            viewModel = viewModel,
-            contentPadding = contentPadding,
-            contentOffset = contentOffset,
-            setGridCoordinates = { gridCoordinates = it },
-            updateDragPositionForRemove = { offset ->
-                isDraggingToRemove =
-                    isPointerWithinCoordinates(
-                        offset = gridCoordinates?.let { it.positionInWindow() + offset },
-                        containerToCheck = removeButtonCoordinates
-                    )
-                isDraggingToRemove
-            },
-            onOpenWidgetPicker = onOpenWidgetPicker,
-            gridState = gridState,
-            contentListState = contentListState,
-            selectedKey = selectedKey,
-            widgetConfigurator = widgetConfigurator,
-        )
+        Column(Modifier.align(Alignment.TopStart)) {
+            CommunalHubLazyGrid(
+                communalContent = communalContent,
+                viewModel = viewModel,
+                contentPadding = contentPadding,
+                contentOffset = contentOffset,
+                setGridCoordinates = { gridCoordinates = it },
+                updateDragPositionForRemove = { offset ->
+                    isDraggingToRemove =
+                        isPointerWithinCoordinates(
+                            offset = gridCoordinates?.let { it.positionInWindow() + offset },
+                            containerToCheck = removeButtonCoordinates
+                        )
+                    isDraggingToRemove
+                },
+                onOpenWidgetPicker = onOpenWidgetPicker,
+                gridState = gridState,
+                contentListState = contentListState,
+                selectedKey = selectedKey,
+                widgetConfigurator = widgetConfigurator,
+            )
+            // TODO(b/326060686): Remove this once keyguard indication area can persist over hub
+            if (viewModel is CommunalViewModel) {
+                val isUnlocked by viewModel.deviceUnlocked.collectAsState(initial = false)
+                Spacer(Modifier.height(24.dp))
+                LockStateIcon(
+                    isUnlocked = isUnlocked,
+                    modifier = Modifier.align(Alignment.CenterHorizontally),
+                )
+            }
+        }
 
         if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
             Toolbar(
@@ -268,7 +281,7 @@
 
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
-private fun BoxScope.CommunalHubLazyGrid(
+private fun ColumnScope.CommunalHubLazyGrid(
     communalContent: List<CommunalContentModel>,
     viewModel: BaseCommunalViewModel,
     contentPadding: PaddingValues,
@@ -282,7 +295,7 @@
     widgetConfigurator: WidgetConfigurator?,
 ) {
     var gridModifier =
-        Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) }
+        Modifier.align(Alignment.Start).onGloballyPositioned { setGridCoordinates(it) }
     var list = communalContent
     var dragDropState: GridDragDropState? = null
     if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
@@ -364,6 +377,26 @@
     }
 }
 
+@Composable
+private fun LockStateIcon(
+    isUnlocked: Boolean,
+    modifier: Modifier = Modifier,
+) {
+    val colors = LocalAndroidColorScheme.current
+    val resource =
+        if (isUnlocked) {
+            R.drawable.ic_unlocked
+        } else {
+            R.drawable.ic_lock
+        }
+    Icon(
+        painter = painterResource(id = resource),
+        contentDescription = null,
+        tint = colors.onPrimaryContainer,
+        modifier = modifier.size(52.dp)
+    )
+}
+
 /**
  * Toolbar that contains action buttons to
  * 1) open the widget picker
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
index b3fcc30..53de5bc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
@@ -27,12 +27,14 @@
 import androidx.compose.animation.slideInVertically
 import androidx.compose.animation.slideOutVertically
 import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.background
 import androidx.compose.foundation.basicMarquee
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
@@ -137,12 +139,14 @@
                     }
                 }
             ) { targetViewModel ->
-                Expandable(
-                    modifier = Modifier.fillMaxSize(),
-                    color = targetViewModel.backgroundColor.toColor(),
-                    shape = RoundedCornerShape(12.dp),
-                    onClick = { viewModel.onDeviceClick(it) },
-                ) {}
+                Spacer(
+                    modifier =
+                        Modifier.fillMaxSize()
+                            .background(
+                                color = targetViewModel.backgroundColor.toColor(),
+                                shape = RoundedCornerShape(12.dp),
+                            ),
+                )
             }
             transition.AnimatedContent(
                 contentKey = { it.icon },
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 37b135e..ce6445b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -23,7 +23,6 @@
 import com.android.systemui.communal.domain.interactor.setCommunalAvailable
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dock.DockManager
 import com.android.systemui.dock.dockManager
 import com.android.systemui.dock.fakeDockManager
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -40,6 +39,7 @@
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -92,6 +92,7 @@
             }
         }
 
+    @Ignore("Ignored until custom animations are implemented in b/322787129")
     @Test
     fun deviceDocked_forceCommunalScene() =
         with(kosmos) {
@@ -110,6 +111,23 @@
         }
 
     @Test
+    fun exitingDream_forceCommunalScene() =
+        with(kosmos) {
+            testScope.runTest {
+                val scene by collectLastValue(communalInteractor.desiredScene)
+                assertThat(scene).isEqualTo(CommunalScenes.Blank)
+
+                updateDocked(true)
+                fakeKeyguardTransitionRepository.sendTransitionSteps(
+                    from = KeyguardState.DREAMING,
+                    to = KeyguardState.LOCKSCREEN,
+                    testScope = this
+                )
+                assertThat(scene).isEqualTo(CommunalScenes.Communal)
+            }
+        }
+
+    @Test
     fun deviceDocked_doesNotForceCommunalIfTransitioningFromCommunal() =
         with(kosmos) {
             testScope.runTest {
@@ -175,6 +193,7 @@
             }
         }
 
+    @Ignore("Ignored until custom animations are implemented in b/322787129")
     @Test
     fun dockingOnLockscreen_forcesCommunal() =
         with(kosmos) {
@@ -196,6 +215,7 @@
             }
         }
 
+    @Ignore("Ignored until custom animations are implemented in b/322787129")
     @Test
     fun dockingOnLockscreen_doesNotForceCommunalIfDreamStarts() =
         with(kosmos) {
@@ -230,7 +250,8 @@
         with(kosmos) {
             runCurrent()
             fakeDockManager.setIsDocked(docked)
-            fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
+            // TODO(b/322787129): uncomment once custom animations are in place
+            // fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
             runCurrent()
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 563aad1..8f802b8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel.Companion.POPUP_AUTO_HIDE_TIMEOUT_MS
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -113,6 +114,7 @@
                 kosmos.communalInteractor,
                 kosmos.communalTutorialInteractor,
                 kosmos.shadeInteractor,
+                kosmos.deviceEntryInteractor,
                 mediaHost,
                 logcatLogBuffer("CommunalViewModelTest"),
             )
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 19b80da..128b465 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
+import com.android.systemui.common.shared.model.Position
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.doze.DozeMachine
 import com.android.systemui.doze.DozeTransitionCallback
@@ -151,6 +152,24 @@
         }
 
     @Test
+    fun clockPosition() =
+        testScope.runTest {
+            assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
+
+            underTest.setClockPosition(0, 1)
+            assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
+
+            underTest.setClockPosition(1, 9)
+            assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
+
+            underTest.setClockPosition(1, 0)
+            assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
+
+            underTest.setClockPosition(3, 1)
+            assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
+        }
+
+    @Test
     fun dozeTimeTick() =
         testScope.runTest {
             val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index 225b5b1..b0f59fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -71,7 +71,7 @@
         mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
 
         MockitoAnnotations.initMocks(this)
-        whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow)
+        whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
         kosmos.burnInInteractor = burnInInteractor
         whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt()))
             .thenReturn(emptyFlow())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
deleted file mode 100644
index ce089b1..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.Flags as AConfigFlags
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.shared.model.BurnInModel
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-
-    @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
-    @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
-
-    @Mock private lateinit var burnInInteractor: BurnInInteractor
-    private val burnInFlow = MutableStateFlow(BurnInModel())
-
-    private lateinit var bottomAreaInteractor: KeyguardBottomAreaInteractor
-    private lateinit var underTest: KeyguardIndicationAreaViewModel
-    private lateinit var repository: FakeKeyguardRepository
-
-    private val startButtonFlow =
-        MutableStateFlow<KeyguardQuickAffordanceViewModel>(
-            KeyguardQuickAffordanceViewModel(
-                slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
-            )
-        )
-    private val endButtonFlow =
-        MutableStateFlow<KeyguardQuickAffordanceViewModel>(
-            KeyguardQuickAffordanceViewModel(
-                slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
-            )
-        )
-    private val alphaFlow = MutableStateFlow<Float>(1f)
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-
-        whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
-            .thenReturn(RETURNED_BURN_IN_OFFSET)
-        whenever(burnInInteractor.burnIn(anyInt(), anyInt())).thenReturn(burnInFlow)
-
-        val withDeps = KeyguardInteractorFactory.create()
-        val keyguardInteractor = withDeps.keyguardInteractor
-        repository = withDeps.repository
-
-        val bottomAreaViewModel: KeyguardBottomAreaViewModel = mock()
-        whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow)
-        whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow)
-        whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow)
-        bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository)
-        underTest =
-            KeyguardIndicationAreaViewModel(
-                keyguardInteractor = keyguardInteractor,
-                bottomAreaInteractor = bottomAreaInteractor,
-                keyguardBottomAreaViewModel = bottomAreaViewModel,
-                burnInHelperWrapper = burnInHelperWrapper,
-                burnInInteractor = burnInInteractor,
-                shortcutsCombinedViewModel = shortcutsCombinedViewModel,
-                configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
-            )
-    }
-
-    @Test
-    fun alpha() =
-        testScope.runTest {
-            val value = collectLastValue(underTest.alpha)
-
-            assertThat(value()).isEqualTo(1f)
-            alphaFlow.value = 0.1f
-            assertThat(value()).isEqualTo(0.1f)
-            alphaFlow.value = 0.5f
-            assertThat(value()).isEqualTo(0.5f)
-            alphaFlow.value = 0.2f
-            assertThat(value()).isEqualTo(0.2f)
-            alphaFlow.value = 0f
-            assertThat(value()).isEqualTo(0f)
-        }
-
-    @Test
-    fun isIndicationAreaPadded() =
-        testScope.runTest {
-            repository.setKeyguardShowing(true)
-            val value = collectLastValue(underTest.isIndicationAreaPadded)
-
-            assertThat(value()).isFalse()
-            startButtonFlow.value = startButtonFlow.value.copy(isVisible = true)
-            assertThat(value()).isTrue()
-            endButtonFlow.value = endButtonFlow.value.copy(isVisible = true)
-            assertThat(value()).isTrue()
-            startButtonFlow.value = startButtonFlow.value.copy(isVisible = false)
-            assertThat(value()).isTrue()
-            endButtonFlow.value = endButtonFlow.value.copy(isVisible = false)
-            assertThat(value()).isFalse()
-        }
-
-    @Test
-    fun indicationAreaTranslationX() =
-        testScope.runTest {
-            val value = collectLastValue(underTest.indicationAreaTranslationX)
-
-            assertThat(value()).isEqualTo(0f)
-            bottomAreaInteractor.setClockPosition(100, 100)
-            assertThat(value()).isEqualTo(100f)
-            bottomAreaInteractor.setClockPosition(200, 100)
-            assertThat(value()).isEqualTo(200f)
-            bottomAreaInteractor.setClockPosition(200, 200)
-            assertThat(value()).isEqualTo(200f)
-            bottomAreaInteractor.setClockPosition(300, 100)
-            assertThat(value()).isEqualTo(300f)
-        }
-
-    @Test
-    fun indicationAreaTranslationY() =
-        testScope.runTest {
-            val value =
-                collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
-
-            // Negative 0 - apparently there's a difference in floating point arithmetic - FML
-            assertThat(value()).isEqualTo(-0f)
-            val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
-            assertThat(value()).isEqualTo(expected1)
-            val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
-            assertThat(value()).isEqualTo(expected2)
-            val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
-            assertThat(value()).isEqualTo(expected3)
-            val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
-            assertThat(value()).isEqualTo(expected4)
-        }
-
-    private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
-        repository.setDozeAmount(dozeAmount)
-        return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
-    }
-
-    companion object {
-        private const val DEFAULT_BURN_IN_OFFSET = 5
-        private const val RETURNED_BURN_IN_OFFSET = 3
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
index 243aab2..dcf635e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
@@ -32,8 +32,8 @@
 import com.android.systemui.volume.localMediaRepository
 import com.android.systemui.volume.mediaController
 import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.mediaOutputActionsInteractor
 import com.android.systemui.volume.mediaOutputInteractor
-import com.android.systemui.volume.panel.mediaOutputActionsInteractor
 import com.android.systemui.volume.panel.volumePanelViewModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml
index 68c8dd9..d8d2985 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf.xml
@@ -19,11 +19,11 @@
         xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
         android:id="@+id/half_shelf_dialog"
         android:orientation="vertical"
-        android:layout_width="wrap_content"
+        android:layout_width="@dimen/large_dialog_width"
         android:layout_height="wrap_content"
         android:layout_gravity="center_horizontal|bottom"
-        android:paddingStart="4dp"
-        android:paddingEnd="4dp">
+        android:paddingLeft="@dimen/dialog_side_padding"
+        android:paddingRight="@dimen/dialog_side_padding">
 
     <LinearLayout
         android:id="@+id/half_shelf"
diff --git a/packages/SystemUI/res/layout/scene_window_root.xml b/packages/SystemUI/res/layout/scene_window_root.xml
index bb8de4c..0dcd15b 100644
--- a/packages/SystemUI/res/layout/scene_window_root.xml
+++ b/packages/SystemUI/res/layout/scene_window_root.xml
@@ -24,7 +24,7 @@
     android:id="@+id/scene_window_root"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:fitsSystemWindows="false">
+    android:fitsSystemWindows="true">
 
     <include layout="@layout/super_notification_shade"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 035cfdc..71ae0d7 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -223,7 +223,6 @@
     <item type="id" name="lock_icon" />
     <item type="id" name="lock_icon_bg" />
     <item type="id" name="burn_in_layer" />
-    <item type="id" name="burn_in_layer_empty_view" />
     <item type="id" name="communal_tutorial_indicator" />
     <item type="id" name="nssl_placeholder_barrier_bottom" />
     <item type="id" name="ambient_indication_container" />
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 617eadb..ce08ca3 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1581,4 +1581,8 @@
     <style name="Theme.PrivacyDialog" parent="@style/Theme.SystemUI.Dialog">
         <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceContainer</item>
     </style>
+
+    <style name="Theme.SystemUI.Dialog.StickyKeys" parent="@style/Theme.SystemUI.Dialog">
+        <item name="android:colorBackground">@color/transparent</item>
+    </style>
 </resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index c0ae4a1..9421f15 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -53,6 +53,9 @@
 import com.android.systemui.animation.ViewHierarchyAnimator;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.plugins.clocks.ClockController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.power.shared.model.ScreenPowerState;
@@ -101,6 +104,7 @@
     private final Rect mClipBounds = new Rect();
     private final KeyguardInteractor mKeyguardInteractor;
     private final PowerInteractor mPowerInteractor;
+    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final DozeParameters mDozeParameters;
 
     private View mStatusArea = null;
@@ -108,6 +112,7 @@
 
     private Boolean mSplitShadeEnabled = false;
     private Boolean mStatusViewCentered = true;
+    private boolean mGoneToAodTransitionRunning = false;
     private DumpManager mDumpManager;
 
     private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
@@ -176,6 +181,7 @@
             KeyguardLogger logger,
             InteractionJankMonitor interactionJankMonitor,
             KeyguardInteractor keyguardInteractor,
+            KeyguardTransitionInteractor keyguardTransitionInteractor,
             DumpManager dumpManager,
             PowerInteractor powerInteractor) {
         super(keyguardStatusView);
@@ -191,6 +197,7 @@
         mDumpManager = dumpManager;
         mKeyguardInteractor = keyguardInteractor;
         mPowerInteractor = powerInteractor;
+        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
     }
 
     @Override
@@ -225,7 +232,6 @@
         mDumpManager.registerDumpable(getInstanceName(), this);
         if (migrateClocksToBlueprint()) {
             startCoroutines(EmptyCoroutineContext.INSTANCE);
-            mView.setVisibility(View.GONE);
         }
     }
 
@@ -241,6 +247,15 @@
                         dozeTimeTick();
                     }
                 }, context);
+
+        collectFlow(mView, mKeyguardTransitionInteractor.getGoneToAodTransition(),
+                (TransitionStep step) -> {
+                    if (step.getTransitionState() == TransitionState.RUNNING) {
+                        mGoneToAodTransitionRunning = true;
+                    } else {
+                        mGoneToAodTransitionRunning = false;
+                    }
+                }, context);
     }
 
     public KeyguardStatusView getView() {
@@ -311,7 +326,7 @@
      * Set keyguard status view alpha.
      */
     public void setAlpha(float alpha) {
-        if (!mKeyguardVisibilityHelper.isVisibilityAnimating()) {
+        if (!mKeyguardVisibilityHelper.isVisibilityAnimating() && !mGoneToAodTransitionRunning) {
             mView.setAlpha(alpha);
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 77054bd..2000028 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -88,10 +88,6 @@
             boolean keyguardFadingAway,
             boolean goingToFullShade,
             int oldStatusBarState) {
-        if (migrateClocksToBlueprint()) {
-            log("Ignoring all of KeyguardVisibilityelper");
-            return;
-        }
         Assert.isMainThread();
         PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA);
         boolean isOccluded = mKeyguardStateController.isOccluded();
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index d5cb4d5..c3c7411 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -24,18 +24,15 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dock.DockManager
-import com.android.systemui.dock.retrieveIsDocked
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.debounce
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.mapLatest
@@ -66,19 +63,21 @@
             .onEach { nextScene -> communalInteractor.onSceneChanged(nextScene) }
             .launchIn(applicationScope)
 
+        // TODO(b/322787129): re-enable once custom animations are in place
         // Handle automatically switching to communal when docked.
-        dockManager
-            .retrieveIsDocked()
-            // Allow some time after docking to ensure the dream doesn't start. If the dream
-            // starts, then we don't want to automatically transition to glanceable hub.
-            .debounce(DOCK_DEBOUNCE_DELAY)
-            .sample(keyguardTransitionInteractor.startedKeyguardState, ::Pair)
-            .onEach { (docked, lastStartedState) ->
-                if (docked && lastStartedState == KeyguardState.LOCKSCREEN) {
-                    communalInteractor.onSceneChanged(CommunalScenes.Communal)
-                }
-            }
-            .launchIn(bgScope)
+        //        dockManager
+        //            .retrieveIsDocked()
+        //            // Allow some time after docking to ensure the dream doesn't start. If the
+        // dream
+        //            // starts, then we don't want to automatically transition to glanceable hub.
+        //            .debounce(DOCK_DEBOUNCE_DELAY)
+        //            .sample(keyguardTransitionInteractor.startedKeyguardState, ::Pair)
+        //            .onEach { (docked, lastStartedState) ->
+        //                if (docked && lastStartedState == KeyguardState.LOCKSCREEN) {
+        //                    communalInteractor.onSceneChanged(CommunalScenes.Communal)
+        //                }
+        //            }
+        //            .launchIn(bgScope)
     }
 
     private suspend fun determineSceneAfterTransition(
@@ -89,7 +88,7 @@
         val docked = dockManager.isDocked
 
         return when {
-            docked && to == KeyguardState.LOCKSCREEN && from != KeyguardState.GLANCEABLE_HUB -> {
+            docked && to == KeyguardState.LOCKSCREEN && from == KeyguardState.DREAMING -> {
                 CommunalScenes.Communal
             }
             to == KeyguardState.GONE -> CommunalScenes.Blank
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index fc9a7df..35b27aa 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
@@ -46,6 +47,7 @@
 import kotlinx.coroutines.launch
 
 /** The default view model used for showing the communal hub. */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class CommunalViewModel
 @Inject
@@ -54,6 +56,7 @@
     private val communalInteractor: CommunalInteractor,
     tutorialInteractor: CommunalTutorialInteractor,
     shadeInteractor: ShadeInteractor,
+    deviceEntryInteractor: DeviceEntryInteractor,
     @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
     @CommunalLog logBuffer: LogBuffer,
 ) : BaseCommunalViewModel(communalInteractor, mediaHost) {
@@ -87,6 +90,8 @@
     /** Whether touches should be disabled in communal */
     val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
 
+    val deviceUnlocked: Flow<Boolean> = deviceEntryInteractor.isUnlocked
+
     init {
         // Initialize our media host for the UMO. This only needs to happen once and must be done
         // before the MediaHierarchyManager attempts to move the UMO to the hub.
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt
index 3501f51..89433d3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeyDialogFactory.kt
@@ -46,7 +46,7 @@
     }
 
     private fun createStickyKeyIndicator(viewModel: StickyKeysIndicatorViewModel): Dialog {
-        return ComponentDialog(context, R.style.Theme_SystemUI_Dialog).apply {
+        return ComponentDialog(context, R.style.Theme_SystemUI_Dialog_StickyKeys).apply {
             // because we're requesting window feature it must be called before setting content
             window?.setStickyKeyWindowAttributes()
             setContentView(createStickyKeyIndicatorView(context, viewModel))
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index ad0c3fb..64e2870 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.biometrics.data.repository.FacePropertyRepository
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
@@ -79,6 +80,12 @@
     val keyguardAlpha: StateFlow<Float>
 
     /**
+     * Observable of the relative offset of the lock-screen clock from its natural position on the
+     * screen.
+     */
+    val clockPosition: StateFlow<Position>
+
+    /**
      * Observable for whether the keyguard is showing.
      *
      * Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in
@@ -233,6 +240,11 @@
     fun setKeyguardAlpha(alpha: Float)
 
     /**
+     * Sets the relative offset of the lock-screen clock from its natural position on the screen.
+     */
+    fun setClockPosition(x: Int, y: Int)
+
+    /**
      * Returns whether the keyguard bottom area should be constrained to the top of the lock icon
      */
     fun isUdfpsSupported(): Boolean
@@ -311,6 +323,9 @@
     private val _keyguardAlpha = MutableStateFlow(1f)
     override val keyguardAlpha = _keyguardAlpha.asStateFlow()
 
+    private val _clockPosition = MutableStateFlow(Position(0, 0))
+    override val clockPosition = _clockPosition.asStateFlow()
+
     private val _clockShouldBeCentered = MutableStateFlow(true)
     override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow()
 
@@ -662,6 +677,10 @@
         _keyguardAlpha.value = alpha
     }
 
+    override fun setClockPosition(x: Int, y: Int) {
+        _clockPosition.value = Position(x, y)
+    }
+
     override fun isUdfpsSupported(): Boolean = keyguardUpdateMonitor.isUdfpsSupported
 
     override fun setQuickSettingsVisible(isVisible: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
index ee892a8..7ae70a9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
@@ -63,19 +63,18 @@
                 burnInHelperWrapper.burnInProgressOffset()
             )
 
-    /** Given the max x,y dimens, determine the current translation shifts. */
-    fun burnIn(xDimenResourceId: Int, yDimenResourceId: Int): Flow<BurnInModel> {
-        return combine(
-                burnInOffset(xDimenResourceId, isXAxis = true),
-                burnInOffset(yDimenResourceId, isXAxis = false).map {
-                    it * 2 - context.resources.getDimensionPixelSize(yDimenResourceId)
+    val keyguardBurnIn: Flow<BurnInModel> =
+        combine(
+                burnInOffset(R.dimen.burn_in_prevention_offset_x, isXAxis = true),
+                burnInOffset(R.dimen.burn_in_prevention_offset_y, isXAxis = false).map {
+                    it * 2 -
+                        context.resources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y)
                 }
             ) { translationX, translationY ->
                 BurnInModel(translationX, translationY, burnInHelperWrapper.burnInScale())
             }
             .distinctUntilChanged()
             .stateIn(scope, SharingStarted.Lazily, BurnInModel())
-    }
 
     /**
      * Use for max burn-in offsets that are NOT specified in pixels. This flow will recalculate the
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 617982f..dbd5e26 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -93,14 +93,22 @@
                     startedKeyguardTransitionStep,
                     keyguardInteractor.isKeyguardOccluded,
                     keyguardInteractor.biometricUnlockState,
+                    keyguardInteractor.primaryBouncerShowing,
                 )
-                .collect { (_, isKeyguardShowing, lastStartedStep, occluded, biometricUnlockState)
-                    ->
+                .collect {
+                    (
+                        _,
+                        isKeyguardShowing,
+                        lastStartedStep,
+                        occluded,
+                        biometricUnlockState,
+                        primaryBouncerShowing) ->
                     if (
                         lastStartedStep.to == KeyguardState.AOD &&
                             !occluded &&
                             !isWakeAndUnlock(biometricUnlockState) &&
-                            isKeyguardShowing
+                            isKeyguardShowing &&
+                            !primaryBouncerShowing
                     ) {
                         val modeOnCanceled =
                             if (lastStartedStep.from == KeyguardState.LOCKSCREEN) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index baa865d..8591fe7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -31,6 +31,8 @@
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.debounce
 import kotlinx.coroutines.launch
 
@@ -59,6 +61,14 @@
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
+    private val canDismissLockScreen: Flow<Boolean> =
+        combine(
+            keyguardInteractor.isKeyguardShowing,
+            keyguardInteractor.isKeyguardDismissible,
+        ) { isKeyguardShowing, isKeyguardDismissible ->
+            isKeyguardDismissible && !isKeyguardShowing
+        }
+
     private fun listenForDozingToAny() {
         scope.launch {
             powerInteractor.isAwake
@@ -68,8 +78,8 @@
                     startedKeyguardTransitionStep,
                     keyguardInteractor.isKeyguardOccluded,
                     communalInteractor.isIdleOnCommunal,
-                    keyguardInteractor.isKeyguardShowing,
-                    keyguardInteractor.isKeyguardDismissible,
+                    canDismissLockScreen,
+                    keyguardInteractor.primaryBouncerShowing,
                 )
                 .collect {
                     (
@@ -78,16 +88,18 @@
                         lastStartedTransition,
                         occluded,
                         isIdleOnCommunal,
-                        isKeyguardShowing,
-                        isKeyguardDismissible) ->
+                        canDismissLockScreen,
+                        primaryBouncerShowing) ->
                     if (!(isAwake && lastStartedTransition.to == KeyguardState.DOZING)) {
                         return@collect
                     }
                     startTransitionTo(
                         if (isWakeAndUnlock(biometricUnlockState)) {
                             KeyguardState.GONE
-                        } else if (isKeyguardDismissible && !isKeyguardShowing) {
+                        } else if (canDismissLockScreen) {
                             KeyguardState.GONE
+                        } else if (primaryBouncerShowing) {
+                            KeyguardState.PRIMARY_BOUNCER
                         } else if (occluded) {
                             KeyguardState.OCCLUDED
                         } else if (isIdleOnCommunal) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
index e5bb5a0..d2a7486 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
@@ -22,8 +22,6 @@
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
 
 /** Encapsulates business-logic specifically related to the keyguard bottom area. */
 @SysUISingleton
@@ -37,12 +35,10 @@
     /** The amount of alpha for the UI components of the bottom area. */
     val alpha: Flow<Float> = repository.bottomAreaAlpha
     /** The position of the keyguard clock. */
-    private val _clockPosition = MutableStateFlow(Position(0, 0))
-    @Deprecated("with migrateClocksToBlueprint()")
-    val clockPosition: Flow<Position> = _clockPosition.asStateFlow()
+    val clockPosition: Flow<Position> = repository.clockPosition
 
     fun setClockPosition(x: Int, y: Int) {
-        _clockPosition.value = Position(x, y)
+        repository.setClockPosition(x, y)
     }
 
     fun setAlpha(alpha: Float) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index fbf1e22..5410b10 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.common.shared.model.NotificationContainerBounds
+import com.android.systemui.common.shared.model.Position
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -231,6 +232,9 @@
     /** The approximate location on the screen of the face unlock sensor, if one is available. */
     val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
 
+    /** The position of the keyguard clock. */
+    val clockPosition: Flow<Position> = repository.clockPosition
+
     @Deprecated("Use the relevant TransitionViewModel")
     val keyguardAlpha: Flow<Float> = repository.keyguardAlpha
 
@@ -338,6 +342,10 @@
         repository.setQuickSettingsVisible(isVisible)
     }
 
+    fun setClockPosition(x: Int, y: Int) {
+        repository.setClockPosition(x, y)
+    }
+
     fun setAlpha(alpha: Float) {
         repository.setKeyguardAlpha(alpha)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 462d5e6..4812e03 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -42,7 +42,7 @@
 import kotlinx.coroutines.launch
 
 private const val TAG = "KeyguardBlueprintViewBinder"
-private const val DEBUG = true
+private const val DEBUG = false
 
 @SysUISingleton
 class KeyguardBlueprintViewBinder
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index e67a324..98bebd0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -21,8 +21,6 @@
 import android.view.View
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
@@ -39,18 +37,13 @@
     private val clockViewModel: KeyguardClockViewModel,
 ) : KeyguardSection() {
     private lateinit var burnInLayer: AodBurnInLayer
-    private lateinit var emptyView: View
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!migrateClocksToBlueprint()) {
             return
         }
 
         // The burn-in layer requires at least 1 view at all times
-        emptyView =
-            View(context, null).apply {
-                id = R.id.burn_in_layer_empty_view
-                visibility = View.GONE
-            }
+        val emptyView = View(context, null).apply { id = View.generateViewId() }
         constraintLayout.addView(emptyView)
         burnInLayer =
             AodBurnInLayer(context).apply {
@@ -77,13 +70,6 @@
         if (!migrateClocksToBlueprint()) {
             return
         }
-
-        constraintSet.apply {
-            // The empty view should not occupy any space
-            constrainHeight(R.id.burn_in_layer_empty_view, 1)
-            constrainWidth(R.id.burn_in_layer_empty_view, 0)
-            connect(R.id.burn_in_layer_empty_view, BOTTOM, PARENT_ID, BOTTOM)
-        }
     }
 
     override fun removeViews(constraintLayout: ConstraintLayout) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
index 4ddd039..6184c82 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
@@ -199,7 +199,7 @@
             private val TRANSITION_PROPERTIES =
                 arrayOf(PROP_VISIBILITY, PROP_ALPHA, PROP_BOUNDS, SMARTSPACE_BOUNDS)
 
-            private val DEBUG = true
+            private val DEBUG = false
             private val TAG = VisibilityBoundsTransition::class.simpleName!!
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index 62fc1da..7be390a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -18,7 +18,6 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import android.util.Log
 import android.util.MathUtils
 import com.android.app.animation.Interpolators
 import com.android.keyguard.KeyguardClockSwitch
@@ -63,8 +62,6 @@
     private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
     private val keyguardClockViewModel: KeyguardClockViewModel,
 ) {
-    private val TAG = "AodBurnInViewModel"
-
     /** Horizontal translation for elements that need to apply anti-burn-in tactics. */
     fun translationX(
         params: BurnInParameters,
@@ -74,17 +71,8 @@
 
     /** Vertical translation for elements that need to apply anti-burn-in tactics. */
     fun translationY(
-        burnInParams: BurnInParameters,
+        params: BurnInParameters,
     ): Flow<Float> {
-        val params =
-            if (burnInParams.minViewY < burnInParams.topInset) {
-                // minViewY should never be below the inset. Correct it if needed
-                Log.w(TAG, "minViewY is below topInset: $burnInParams")
-                burnInParams.copy(minViewY = burnInParams.topInset)
-            } else {
-                burnInParams
-            }
-
         return configurationInteractor
             .dimensionPixelSize(R.dimen.keyguard_enter_from_top_translation_y)
             .flatMapLatest { enterFromTopAmount ->
@@ -137,10 +125,7 @@
             keyguardTransitionInteractor.dozeAmountTransition.map {
                 Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value)
             },
-            burnInInteractor.burnIn(
-                xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
-                yDimenResourceId = R.dimen.burn_in_prevention_offset_y
-            )
+            burnInInteractor.keyguardBurnIn,
         ) { interpolated, burnIn ->
             val useScaleOnly =
                 (clockController(params.clockControllerProvider)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 31e8093..bd19c80 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -180,7 +180,7 @@
         }
 
     private val isUnlocked: Flow<Boolean> =
-        deviceEntryInteractor.isUnlocked.flatMapLatest { isUnlocked ->
+        keyguardInteractor.isKeyguardDismissible.flatMapLatest { isUnlocked ->
             if (!isUnlocked) {
                 flowOf(false)
             } else {
@@ -197,7 +197,7 @@
     val iconType: Flow<DeviceEntryIconView.IconType> =
         combine(
             deviceEntryUdfpsInteractor.isListeningForUdfps,
-            keyguardInteractor.isKeyguardDismissible,
+            isUnlocked,
         ) { isListeningForUdfps, isUnlocked ->
             if (isListeningForUdfps) {
                 DeviceEntryIconView.IconType.FINGERPRINT
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
index e35e065..6458eda 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
@@ -17,14 +17,10 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
-import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.shared.model.BurnInModel
-import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -36,10 +32,9 @@
 @Inject
 constructor(
     private val keyguardInteractor: KeyguardInteractor,
-    private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
+    bottomAreaInteractor: KeyguardBottomAreaInteractor,
     keyguardBottomAreaViewModel: KeyguardBottomAreaViewModel,
     private val burnInHelperWrapper: BurnInHelperWrapper,
-    private val burnInInteractor: BurnInInteractor,
     private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
     configurationInteractor: ConfigurationInteractor,
 ) {
@@ -68,37 +63,24 @@
                 }
                 .distinctUntilChanged()
         }
-
-    private val burnIn: Flow<BurnInModel> =
-        burnInInteractor
-            .burnIn(
-                xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
-                yDimenResourceId = R.dimen.default_burn_in_prevention_offset,
-            )
-            .distinctUntilChanged()
-
     /** An observable for the x-offset by which the indication area should be translated. */
     val indicationAreaTranslationX: Flow<Float> =
-        if (migrateClocksToBlueprint() || keyguardBottomAreaRefactor()) {
-            burnIn.map { it.translationX.toFloat() }
+        if (keyguardBottomAreaRefactor()) {
+            keyguardInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
         } else {
             bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
         }
 
     /** Returns an observable for the y-offset by which the indication area should be translated. */
     fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
-        return if (migrateClocksToBlueprint()) {
-            burnIn.map { it.translationY.toFloat() }
-        } else {
-            keyguardInteractor.dozeAmount
-                .map { dozeAmount ->
-                    dozeAmount *
-                        (burnInHelperWrapper.burnInOffset(
-                            /* amplitude = */ defaultBurnInOffset * 2,
-                            /* xAxis= */ false,
-                        ) - defaultBurnInOffset)
-                }
-                .distinctUntilChanged()
-        }
+        return keyguardInteractor.dozeAmount
+            .map { dozeAmount ->
+                dozeAmount *
+                    (burnInHelperWrapper.burnInOffset(
+                        /* amplitude = */ defaultBurnInOffset * 2,
+                        /* xAxis= */ false,
+                    ) - defaultBurnInOffset)
+            }
+            .distinctUntilChanged()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 38d5e0f..1760b92 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -75,6 +75,7 @@
     private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
     private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel,
     private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
+    private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
     private val glanceableHubToLockscreenTransitionViewModel:
         GlanceableHubToLockscreenTransitionViewModel,
     private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
@@ -171,6 +172,7 @@
                         aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
                         dozingToGoneTransitionViewModel.lockscreenAlpha(viewState),
                         dozingToLockscreenTransitionViewModel.lockscreenAlpha,
+                        dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
                         glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
                         goneToAodTransitionViewModel.enterFromTopAnimationAlpha,
                         goneToDozingTransitionViewModel.lockscreenAlpha,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 306aad1..75bf131 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -156,6 +156,7 @@
      * desired scene. Once enough of the transition has occurred, the [currentScene] will become
      * [toScene] (unless the transition is canceled by user action or another call to this method).
      */
+    @JvmOverloads
     fun changeScene(
         toScene: SceneKey,
         loggingReason: String,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
index 22645c4..ea19020 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/WindowRootView.kt
@@ -71,7 +71,6 @@
 
     override fun onApplyWindowInsets(windowInsets: WindowInsets): WindowInsets? {
         val insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
-        val displayCutout = rootWindowInsets.displayCutout
         if (fitsSystemWindows) {
             val paddingChanged = insets.top != paddingTop || insets.bottom != paddingBottom
 
@@ -79,23 +78,22 @@
             if (paddingChanged) {
                 setPadding(0, 0, 0, 0)
             }
-
-            val pairInsets: Pair<Int, Int> =
-                layoutInsetsController.getinsets(windowInsets, displayCutout)
-            leftInset = pairInsets.first
-            rightInset = pairInsets.second
-            applyMargins()
         } else {
             val changed =
                 paddingLeft != 0 || paddingRight != 0 || paddingTop != 0 || paddingBottom != 0
             if (changed) {
                 setPadding(0, 0, 0, 0)
             }
-
-            leftInset = 0
-            rightInset = 0
         }
+        leftInset = 0
+        rightInset = 0
 
+        val displayCutout = rootWindowInsets.displayCutout
+        val pairInsets: Pair<Int, Int> =
+            layoutInsetsController.getinsets(windowInsets, displayCutout)
+        leftInset = pairInsets.first
+        rightInset = pairInsets.second
+        applyMargins()
         return windowInsets
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
new file mode 100644
index 0000000..2294fc0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.animation.Animator
+import android.app.Notification
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.view.Display
+import android.view.LayoutInflater
+import android.view.ScrollCaptureResponse
+import android.view.View
+import android.view.ViewTreeObserver
+import android.view.WindowInsets
+import android.window.OnBackInvokedDispatcher
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.res.R
+
+/**
+ * Legacy implementation of screenshot view methods. Just proxies the calls down into the original
+ * ScreenshotView.
+ */
+class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy {
+    override val view: ScreenshotView =
+        LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView
+    override val internalInsetsListener: ViewTreeObserver.OnComputeInternalInsetsListener
+    override val screenshotPreview: View
+
+    override var defaultDisplay: Int = Display.DEFAULT_DISPLAY
+        set(value) {
+            view.setDefaultDisplay(value)
+        }
+    override var defaultTimeoutMillis: Long = 6000
+        set(value) {
+            view.setDefaultTimeoutMillis(value)
+        }
+    override var onKeyListener: View.OnKeyListener? = null
+        set(value) {
+            view.setOnKeyListener(value)
+        }
+    override var flags: FeatureFlags? = null
+        set(value) {
+            view.setFlags(value)
+        }
+    override var packageName: String = ""
+        set(value) {
+            view.setPackageName(value)
+        }
+    override var logger: UiEventLogger? = null
+        set(value) {
+            view.setUiEventLogger(value)
+        }
+    override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
+        set(value) {
+            view.setCallbacks(value)
+        }
+    override var screenshot: ScreenshotData? = null
+        set(value) {
+            view.setScreenshot(value)
+        }
+
+    override val isAttachedToWindow
+        get() = view.isAttachedToWindow
+    override val isDismissing
+        get() = view.isDismissing
+    override val isPendingSharedTransition
+        get() = view.isPendingSharedTransition
+
+    init {
+        internalInsetsListener = view
+        screenshotPreview = view.screenshotPreview
+    }
+
+    override fun reset() = view.reset()
+    override fun updateInsets(insets: WindowInsets) = view.updateInsets(insets)
+    override fun updateOrientation(insets: WindowInsets) = view.updateOrientation(insets)
+
+    override fun badgeScreenshot(userBadgedIcon: Drawable) = view.badgeScreenshot(userBadgedIcon)
+
+    override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator =
+        view.createScreenshotDropInAnimation(screenRect, showFlash)
+
+    override fun addQuickShareChip(quickShareAction: Notification.Action) =
+        view.addQuickShareChip(quickShareAction)
+
+    override fun setChipIntents(imageData: ScreenshotController.SavedImageData) =
+        view.setChipIntents(imageData)
+
+    override fun animateDismissal() = view.animateDismissal()
+
+    override fun showScrollChip(packageName: String, onClick: Runnable) =
+        view.showScrollChip(packageName, onClick)
+
+    override fun hideScrollChip() = view.hideScrollChip()
+
+    override fun prepareScrollingTransition(
+        response: ScrollCaptureResponse,
+        screenBitmap: Bitmap,
+        newScreenshot: Bitmap,
+        screenshotTakenInPortrait: Boolean
+    ) =
+        view.prepareScrollingTransition(
+            response,
+            screenBitmap,
+            newScreenshot,
+            screenshotTakenInPortrait
+        )
+
+    override fun startLongScreenshotTransition(
+        transitionDestination: Rect,
+        onTransitionEnd: Runnable,
+        longScreenshot: ScrollCaptureController.LongScreenshot
+    ) = view.startLongScreenshotTransition(transitionDestination, onTransitionEnd, longScreenshot)
+
+    override fun restoreNonScrollingUi() = view.restoreNonScrollingUi()
+
+    override fun stopInputListening() = view.stopInputListening()
+
+    override fun requestFocus() {
+        view.requestFocus()
+    }
+
+    override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
+
+    override fun addOnAttachStateChangeListener(listener: View.OnAttachStateChangeListener) =
+        view.addOnAttachStateChangeListener(listener)
+
+    override fun findOnBackInvokedDispatcher(): OnBackInvokedDispatcher? =
+        view.findOnBackInvokedDispatcher()
+
+    override fun getViewTreeObserver(): ViewTreeObserver = view.viewTreeObserver
+
+    override fun post(runnable: Runnable) {
+        view.post(runnable)
+    }
+
+    class Factory : ScreenshotViewProxy.Factory {
+        override fun getProxy(context: Context): ScreenshotViewProxy {
+            return LegacyScreenshotViewProxy(context)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index ee3e7ba..13448d2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -65,7 +65,6 @@
 import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
 import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationTarget;
 import android.view.ScrollCaptureResponse;
@@ -165,7 +164,7 @@
     /**
      * Structure returned by the SaveImageInBackgroundTask
      */
-    static class SavedImageData {
+    public static class SavedImageData {
         public Uri uri;
         public List<Notification.Action> smartActions;
         public Notification.Action quickShareAction;
@@ -237,6 +236,7 @@
 
     private final WindowContext mContext;
     private final FeatureFlags mFlags;
+    private final ScreenshotViewProxy mViewProxy;
     private final ScreenshotNotificationsController mNotificationsController;
     private final ScreenshotSmartActions mScreenshotSmartActions;
     private final UiEventLogger mUiEventLogger;
@@ -272,7 +272,6 @@
         respondToKeyDismissal();
     };
 
-    private ScreenshotView mScreenshotView;
     private final MessageContainerController mMessageContainerController;
     private Bitmap mScreenBitmap;
     private SaveImageInBackgroundTask mSaveInBgTask;
@@ -305,6 +304,7 @@
     ScreenshotController(
             Context context,
             FeatureFlags flags,
+            ScreenshotViewProxy.Factory viewProxyFactory,
             ScreenshotSmartActions screenshotSmartActions,
             ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
             ScrollCaptureClient scrollCaptureClient,
@@ -360,6 +360,8 @@
         mMessageContainerController = messageContainerController;
         mAssistContentRequester = assistContentRequester;
 
+        mViewProxy = viewProxyFactory.getProxy(mContext);
+
         mAccessibilityManager = AccessibilityManager.getInstance(mContext);
 
         // Setup the window that we are going to use
@@ -461,7 +463,7 @@
 
         // The window is focusable by default
         setWindowFocusable(true);
-        mScreenshotView.requestFocus();
+        mViewProxy.requestFocus();
 
         enqueueScrollCaptureRequest(screenshot.getUserHandle());
 
@@ -485,10 +487,10 @@
             mMessageContainerController.onScreenshotTaken(screenshot);
         });
 
-        mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
+        mViewProxy.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
                 mContext.getDrawable(R.drawable.overlay_badge_background),
                 screenshot.getUserHandle()));
-        mScreenshotView.setScreenshot(screenshot);
+        mViewProxy.setScreenshot(screenshot);
 
         // ignore system bar insets for the purpose of window layout
         mWindow.getDecorView().setOnApplyWindowInsetsListener(
@@ -503,31 +505,31 @@
     void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) {
         withWindowAttached(() -> {
             if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
-                mScreenshotView.announceForAccessibility(mContext.getResources().getString(
+                mViewProxy.announceForAccessibility(mContext.getResources().getString(
                         R.string.screenshot_saving_work_profile_title));
             } else {
-                mScreenshotView.announceForAccessibility(
+                mViewProxy.announceForAccessibility(
                         mContext.getResources().getString(R.string.screenshot_saving_title));
             }
         });
 
-        mScreenshotView.reset();
+        mViewProxy.reset();
 
-        if (mScreenshotView.isAttachedToWindow()) {
+        if (mViewProxy.isAttachedToWindow()) {
             // if we didn't already dismiss for another reason
-            if (!mScreenshotView.isDismissing()) {
+            if (!mViewProxy.isDismissing()) {
                 mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0,
                         oldPackageName);
             }
             if (DEBUG_WINDOW) {
                 Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "
-                        + "(dismissing=" + mScreenshotView.isDismissing() + ")");
+                        + "(dismissing=" + mViewProxy.isDismissing() + ")");
             }
         }
 
-        mScreenshotView.setPackageName(mPackageName);
+        mViewProxy.setPackageName(mPackageName);
 
-        mScreenshotView.updateOrientation(
+        mViewProxy.updateOrientation(
                 mWindowManager.getCurrentWindowMetrics().getWindowInsets());
     }
 
@@ -539,7 +541,7 @@
             Log.d(TAG, "dismissScreenshot");
         }
         // If we're already animating out, don't restart the animation
-        if (mScreenshotView.isDismissing()) {
+        if (mViewProxy.isDismissing()) {
             if (DEBUG_DISMISS) {
                 Log.v(TAG, "Already dismissing, ignoring duplicate command");
             }
@@ -547,11 +549,11 @@
         }
         mUiEventLogger.log(event, 0, mPackageName);
         mScreenshotHandler.cancelTimeout();
-        mScreenshotView.animateDismissal();
+        mViewProxy.animateDismissal();
     }
 
     boolean isPendingSharedTransition() {
-        return mScreenshotView.isPendingSharedTransition();
+        return mViewProxy.isPendingSharedTransition();
     }
 
     // Any cleanup needed when the service is being destroyed.
@@ -576,7 +578,7 @@
 
     private void releaseMediaPlayer() {
         if (mScreenshotSoundController == null) return;
-        mScreenshotSoundController.releaseScreenshotSound();
+        mScreenshotSoundController.releaseScreenshotSoundAsync();
     }
 
     private void respondToKeyDismissal() {
@@ -591,18 +593,15 @@
             Log.d(TAG, "reloadAssets()");
         }
 
-        // Inflate the screenshot layout
-        mScreenshotView = (ScreenshotView)
-                LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
-        mMessageContainerController.setView(mScreenshotView);
-        mScreenshotView.addOnAttachStateChangeListener(
+        mMessageContainerController.setView(mViewProxy.getView());
+        mViewProxy.addOnAttachStateChangeListener(
                 new View.OnAttachStateChangeListener() {
                     @Override
                     public void onViewAttachedToWindow(@NonNull View v) {
                         if (DEBUG_INPUT) {
                             Log.d(TAG, "Registering Predictive Back callback");
                         }
-                        mScreenshotView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+                        mViewProxy.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
                                 OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
                     }
 
@@ -611,11 +610,12 @@
                         if (DEBUG_INPUT) {
                             Log.d(TAG, "Unregistering Predictive Back callback");
                         }
-                        mScreenshotView.findOnBackInvokedDispatcher()
+                        mViewProxy.findOnBackInvokedDispatcher()
                                 .unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
                     }
                 });
-        mScreenshotView.init(mUiEventLogger, new ScreenshotView.ScreenshotViewCallback() {
+        mViewProxy.setLogger(mUiEventLogger);
+        mViewProxy.setCallbacks(new ScreenshotView.ScreenshotViewCallback() {
             @Override
             public void onUserInteraction() {
                 if (DEBUG_INPUT) {
@@ -640,11 +640,12 @@
                 // TODO(159460485): Remove this when focus is handled properly in the system
                 setWindowFocusable(false);
             }
-        }, mFlags);
-        mScreenshotView.setDefaultDisplay(mDisplayId);
-        mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
+        });
+        mViewProxy.setFlags(mFlags);
+        mViewProxy.setDefaultDisplay(mDisplayId);
+        mViewProxy.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
 
-        mScreenshotView.setOnKeyListener((v, keyCode, event) -> {
+        mViewProxy.setOnKeyListener((v, keyCode, event) -> {
             if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
                 if (DEBUG_INPUT) {
                     Log.d(TAG, "onKeyEvent: " + keyCode);
@@ -658,23 +659,24 @@
         if (DEBUG_WINDOW) {
             Log.d(TAG, "adding OnComputeInternalInsetsListener");
         }
-        mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(mScreenshotView);
+        mViewProxy.getViewTreeObserver().addOnComputeInternalInsetsListener(
+                mViewProxy.getInternalInsetsListener());
         if (DEBUG_WINDOW) {
-            Log.d(TAG, "setContentView: " + mScreenshotView);
+            Log.d(TAG, "setContentView: " + mViewProxy.getView());
         }
-        setContentView(mScreenshotView);
+        setContentView(mViewProxy.getView());
     }
 
     private void prepareAnimation(Rect screenRect, boolean showFlash,
             Runnable onAnimationComplete) {
-        mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
+        mViewProxy.getViewTreeObserver().addOnPreDrawListener(
                 new ViewTreeObserver.OnPreDrawListener() {
                     @Override
                     public boolean onPreDraw() {
                         if (DEBUG_WINDOW) {
                             Log.d(TAG, "onPreDraw: startAnimation");
                         }
-                        mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this);
+                        mViewProxy.getViewTreeObserver().removeOnPreDrawListener(this);
                         startAnimation(screenRect, showFlash, onAnimationComplete);
                         return true;
                     }
@@ -694,13 +696,13 @@
                             if (mConfigChanges.applyNewConfig(mContext.getResources())) {
                                 // Hide the scroll chip until we know it's available in this
                                 // orientation
-                                mScreenshotView.hideScrollChip();
+                                mViewProxy.hideScrollChip();
                                 // Delay scroll capture eval a bit to allow the underlying activity
                                 // to set up in the new orientation.
                                 mScreenshotHandler.postDelayed(() -> {
                                     requestScrollCapture(owner);
                                 }, 150);
-                                mScreenshotView.updateInsets(
+                                mViewProxy.updateInsets(
                                         mWindowManager.getCurrentWindowMetrics().getWindowInsets());
                                 // Screenshot animation calculations won't be valid anymore,
                                 // so just end
@@ -759,16 +761,16 @@
                     + mLastScrollCaptureResponse.getWindowTitle() + "]");
 
             final ScrollCaptureResponse response = mLastScrollCaptureResponse;
-            mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> {
+            mViewProxy.showScrollChip(response.getPackageName(), /* onClick */ () -> {
                 DisplayMetrics displayMetrics = new DisplayMetrics();
                 getDisplay().getRealMetrics(displayMetrics);
                 Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId,
                         new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
 
-                mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
+                mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
                         mScreenshotTakenInPortrait);
                 // delay starting scroll capture to make sure the scrim is up before the app moves
-                mScreenshotView.post(() -> runBatchScrollCapture(response, owner));
+                mViewProxy.post(() -> runBatchScrollCapture(response, owner));
             });
         } catch (InterruptedException | ExecutionException e) {
             Log.e(TAG, "requestScrollCapture failed", e);
@@ -794,19 +796,19 @@
                 return;
             } catch (InterruptedException | ExecutionException e) {
                 Log.e(TAG, "Exception", e);
-                mScreenshotView.restoreNonScrollingUi();
+                mViewProxy.restoreNonScrollingUi();
                 return;
             }
 
             if (longScreenshot.getHeight() == 0) {
-                mScreenshotView.restoreNonScrollingUi();
+                mViewProxy.restoreNonScrollingUi();
                 return;
             }
 
             mLongScreenshotHolder.setLongScreenshot(longScreenshot);
             mLongScreenshotHolder.setTransitionDestinationCallback(
                     (transitionDestination, onTransitionEnd) -> {
-                        mScreenshotView.startLongScreenshotTransition(
+                        mViewProxy.startLongScreenshotTransition(
                                 transitionDestination, onTransitionEnd,
                                 longScreenshot);
                         // TODO: Do this via ActionIntentExecutor instead.
@@ -882,16 +884,14 @@
             }
             mWindowManager.removeViewImmediate(decorView);
         }
-        // Ensure that we remove the input monitor
-        if (mScreenshotView != null) {
-            mScreenshotView.stopInputListening();
-        }
+
+        mViewProxy.stopInputListening();
     }
 
     private void playCameraSoundIfNeeded() {
         if (mScreenshotSoundController == null) return;
         // the controller is not-null only on the default display controller
-        mScreenshotSoundController.playCameraSound();
+        mScreenshotSoundController.playScreenshotSoundAsync();
     }
 
     /**
@@ -932,7 +932,7 @@
         }
 
         mScreenshotAnimation =
-                mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash);
+                mViewProxy.createScreenshotDropInAnimation(screenRect, showFlash);
         if (onAnimationComplete != null) {
             mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
                 @Override
@@ -975,7 +975,7 @@
                 };
         Pair<ActivityOptions, ExitTransitionCoordinator> transition =
                 ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null,
-                        Pair.create(mScreenshotView.getScreenshotPreview(),
+                        Pair.create(mViewProxy.getScreenshotPreview(),
                                 ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
 
         return transition;
@@ -999,7 +999,7 @@
             mCurrentRequestCallback.onFinish();
             mCurrentRequestCallback = null;
         }
-        mScreenshotView.reset();
+        mViewProxy.reset();
         removeWindow();
         mScreenshotHandler.cancelTimeout();
     }
@@ -1067,7 +1067,7 @@
     }
 
     private void doPostAnimation(ScreenshotController.SavedImageData imageData) {
-        mScreenshotView.setChipIntents(imageData);
+        mViewProxy.setChipIntents(imageData);
     }
 
     /**
@@ -1084,11 +1084,11 @@
                         @Override
                         public void onAnimationEnd(Animator animation) {
                             super.onAnimationEnd(animation);
-                            mScreenshotView.addQuickShareChip(quickShareData.quickShareAction);
+                            mViewProxy.addQuickShareChip(quickShareData.quickShareAction);
                         }
                     });
                 } else {
-                    mScreenshotView.addQuickShareChip(quickShareData.quickShareAction);
+                    mViewProxy.addQuickShareChip(quickShareData.quickShareAction);
                 }
             });
         }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
index 2c0bdde..d3a7fc4a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
@@ -21,22 +21,34 @@
 import com.android.app.tracing.coroutines.async
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.google.errorprone.annotations.CanIgnoreReturnValue
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import kotlinx.coroutines.withTimeout
 
 /** Controls sound reproduction after a screenshot is taken. */
 interface ScreenshotSoundController {
     /** Reproduces the camera sound. */
-    @CanIgnoreReturnValue fun playCameraSound(): Deferred<Unit>
+    suspend fun playScreenshotSound()
 
-    /** Releases the sound. [playCameraSound] behaviour is undefined after this has been called. */
-    @CanIgnoreReturnValue fun releaseScreenshotSound(): Deferred<Unit>
+    /**
+     * Releases the sound. [playScreenshotSound] behaviour is undefined after this has been called.
+     */
+    suspend fun releaseScreenshotSound()
+
+    /** Reproduces the camera sound. Used for compatibility with Java code. */
+    fun playScreenshotSoundAsync()
+
+    /**
+     * Releases the sound. [playScreenshotSound] behaviour is undefined after this has been called.
+     * Used for compatibility with Java code.
+     */
+    fun releaseScreenshotSoundAsync()
 }
 
 class ScreenshotSoundControllerImpl
@@ -47,8 +59,8 @@
     @Background private val bgDispatcher: CoroutineDispatcher
 ) : ScreenshotSoundController {
 
-    val player: Deferred<MediaPlayer?> =
-        coroutineScope.async("loadCameraSound", bgDispatcher) {
+    private val player: Deferred<MediaPlayer?> =
+        coroutineScope.async("loadScreenshotSound", bgDispatcher) {
             try {
                 soundProvider.getScreenshotSound()
             } catch (e: IllegalStateException) {
@@ -57,8 +69,8 @@
             }
         }
 
-    override fun playCameraSound(): Deferred<Unit> {
-        return coroutineScope.async("playCameraSound", bgDispatcher) {
+    override suspend fun playScreenshotSound() {
+        withContext(bgDispatcher) {
             try {
                 player.await()?.start()
             } catch (e: IllegalStateException) {
@@ -68,8 +80,8 @@
         }
     }
 
-    override fun releaseScreenshotSound(): Deferred<Unit> {
-        return coroutineScope.async("releaseScreenshotSound", bgDispatcher) {
+    override suspend fun releaseScreenshotSound() {
+        withContext(bgDispatcher) {
             try {
                 withTimeout(1.seconds) { player.await()?.release() }
             } catch (e: TimeoutCancellationException) {
@@ -79,6 +91,14 @@
         }
     }
 
+    override fun playScreenshotSoundAsync() {
+        coroutineScope.launch { playScreenshotSound() }
+    }
+
+    override fun releaseScreenshotSoundAsync() {
+        coroutineScope.launch { releaseScreenshotSound() }
+    }
+
     private companion object {
         const val TAG = "ScreenshotSoundControllerImpl"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index be30a15..8a8766d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -102,7 +102,7 @@
 public class ScreenshotView extends FrameLayout implements
         ViewTreeObserver.OnComputeInternalInsetsListener {
 
-    interface ScreenshotViewCallback {
+    public interface ScreenshotViewCallback {
         void onUserInteraction();
 
         void onAction(Intent intent, UserHandle owner, boolean overrideTransition);
@@ -426,15 +426,15 @@
         return mScreenshotPreview;
     }
 
-    /**
-     * Set up the logger and callback on dismissal.
-     *
-     * Note: must be called before any other (non-constructor) method or null pointer exceptions
-     * may occur.
-     */
-    void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks, FeatureFlags flags) {
+    void setUiEventLogger(UiEventLogger uiEventLogger) {
         mUiEventLogger = uiEventLogger;
+    }
+
+    void setCallbacks(ScreenshotViewCallback callbacks) {
         mCallbacks = callbacks;
+    }
+
+    void setFlags(FeatureFlags flags) {
         mFlags = flags;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
new file mode 100644
index 0000000..0064521
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.animation.Animator
+import android.app.Notification
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.view.ScrollCaptureResponse
+import android.view.View
+import android.view.View.OnKeyListener
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import android.view.WindowInsets
+import android.window.OnBackInvokedDispatcher
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.flags.FeatureFlags
+
+/** Abstraction of the surface between ScreenshotController and ScreenshotView */
+interface ScreenshotViewProxy {
+    val view: ViewGroup
+    val internalInsetsListener: ViewTreeObserver.OnComputeInternalInsetsListener
+    val screenshotPreview: View
+
+    var defaultDisplay: Int
+    var defaultTimeoutMillis: Long
+    var onKeyListener: OnKeyListener?
+    var flags: FeatureFlags?
+    var packageName: String
+    var logger: UiEventLogger?
+    var callbacks: ScreenshotView.ScreenshotViewCallback?
+    var screenshot: ScreenshotData?
+
+    val isAttachedToWindow: Boolean
+    val isDismissing: Boolean
+    val isPendingSharedTransition: Boolean
+
+    fun reset()
+    fun updateInsets(insets: WindowInsets)
+    fun updateOrientation(insets: WindowInsets)
+    fun badgeScreenshot(userBadgedIcon: Drawable)
+    fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator
+    fun addQuickShareChip(quickShareAction: Notification.Action)
+    fun setChipIntents(imageData: ScreenshotController.SavedImageData)
+    fun animateDismissal()
+
+    fun showScrollChip(packageName: String, onClick: Runnable)
+    fun hideScrollChip()
+    fun prepareScrollingTransition(
+        response: ScrollCaptureResponse,
+        screenBitmap: Bitmap,
+        newScreenshot: Bitmap,
+        screenshotTakenInPortrait: Boolean
+    )
+    fun startLongScreenshotTransition(
+        transitionDestination: Rect,
+        onTransitionEnd: Runnable,
+        longScreenshot: ScrollCaptureController.LongScreenshot
+    )
+    fun restoreNonScrollingUi()
+
+    fun stopInputListening()
+    fun requestFocus()
+    fun announceForAccessibility(string: String)
+    fun addOnAttachStateChangeListener(listener: View.OnAttachStateChangeListener)
+    fun findOnBackInvokedDispatcher(): OnBackInvokedDispatcher?
+    fun getViewTreeObserver(): ViewTreeObserver
+    fun post(runnable: Runnable)
+
+    interface Factory {
+        fun getProxy(context: Context): ScreenshotViewProxy
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index bb34ede..8a2678c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -75,7 +75,7 @@
     private String mWindowOwner;
     private volatile boolean mCancelled;
 
-    static class LongScreenshot {
+    public static class LongScreenshot {
         private final ImageTileSet mImageTileSet;
         private final Session mSession;
         // TODO: Add UserHandle so LongScreenshots can adhere to work profile screenshot policy
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 3797b8b..a00c81d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -20,6 +20,7 @@
 
 import com.android.systemui.screenshot.ImageCapture;
 import com.android.systemui.screenshot.ImageCaptureImpl;
+import com.android.systemui.screenshot.LegacyScreenshotViewProxy;
 import com.android.systemui.screenshot.RequestProcessor;
 import com.android.systemui.screenshot.ScreenshotPolicy;
 import com.android.systemui.screenshot.ScreenshotPolicyImpl;
@@ -29,12 +30,14 @@
 import com.android.systemui.screenshot.ScreenshotSoundControllerImpl;
 import com.android.systemui.screenshot.ScreenshotSoundProvider;
 import com.android.systemui.screenshot.ScreenshotSoundProviderImpl;
+import com.android.systemui.screenshot.ScreenshotViewProxy;
 import com.android.systemui.screenshot.TakeScreenshotService;
 import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService;
 import com.android.systemui.screenshot.appclips.AppClipsService;
 
 import dagger.Binds;
 import dagger.Module;
+import dagger.Provides;
 import dagger.multibindings.ClassKey;
 import dagger.multibindings.IntoMap;
 
@@ -81,4 +84,9 @@
     @Binds
     abstract ScreenshotSoundController bindScreenshotSoundController(
             ScreenshotSoundControllerImpl screenshotSoundProviderImpl);
+
+    @Provides
+    static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory() {
+        return new LegacyScreenshotViewProxy.Factory();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index cf7c3e0..1566de5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1288,9 +1288,10 @@
                             mView.getContext().getDisplay());
             mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
             mKeyguardStatusViewController.init();
+        }
 
-            mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
-            mKeyguardStatusViewController.getView().addOnLayoutChangeListener(
+        mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
+        mKeyguardStatusViewController.getView().addOnLayoutChangeListener(
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
                     int oldHeight = oldBottom - oldTop;
                     if (v.getHeight() != oldHeight) {
@@ -1298,8 +1299,7 @@
                     }
                 });
 
-            updateClockAppearance();
-        }
+        updateClockAppearance();
     }
 
     @Override
@@ -1326,9 +1326,7 @@
 
     private void onSplitShadeEnabledChanged() {
         mShadeLog.logSplitShadeChanged(mSplitShadeEnabled);
-        if (!migrateClocksToBlueprint()) {
-            mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
-        }
+        mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
         // Reset any left over overscroll state. It is a rare corner case but can happen.
         mQsController.setOverScrollAmount(0);
         mScrimController.setNotificationsOverScrollAmount(0);
@@ -1443,13 +1441,11 @@
         mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
                 mStatusBarStateController.getInterpolatedDozeAmount());
 
-        if (!migrateClocksToBlueprint()) {
-            mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
-                    mBarState,
-                    false,
-                    false,
-                    mBarState);
-        }
+        mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+                mBarState,
+                false,
+                false,
+                mBarState);
         if (mKeyguardQsUserSwitchController != null) {
             mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
                     mBarState,
@@ -1669,11 +1665,13 @@
             mKeyguardStatusViewController.setLockscreenClockY(
                     mClockPositionAlgorithm.getExpandedPreferredClockY());
         }
-        if (!(migrateClocksToBlueprint() || keyguardBottomAreaRefactor())) {
+        if (keyguardBottomAreaRefactor()) {
+            mKeyguardInteractor.setClockPosition(
+                mClockPositionResult.clockX, mClockPositionResult.clockY);
+        } else {
             mKeyguardBottomAreaInteractor.setClockPosition(
                 mClockPositionResult.clockX, mClockPositionResult.clockY);
         }
-
         boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
         boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
 
@@ -1751,11 +1749,13 @@
     }
 
     private void updateKeyguardStatusViewAlignment(boolean animate) {
-        if (migrateClocksToBlueprint()) {
-            return;
-        }
         boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
-        ConstraintLayout layout = mNotificationContainerParent;
+        ConstraintLayout layout;
+        if (migrateClocksToBlueprint()) {
+            layout = mKeyguardViewConfigurator.getKeyguardRootView();
+        } else {
+            layout = mNotificationContainerParent;
+        }
         mKeyguardStatusViewController.updateAlignment(
                 layout, mSplitShadeEnabled, shouldBeCentered, animate);
         mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered));
@@ -3316,9 +3316,6 @@
         /** Updates the views to the initial state for the fold to AOD animation. */
         @Override
         public void prepareFoldToAodAnimation() {
-            if (migrateClocksToBlueprint()) {
-                return;
-            }
             // Force show AOD UI even if we are not locked
             showAodUi();
 
@@ -3340,9 +3337,6 @@
         @Override
         public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
                 Runnable cancelAction) {
-            if (migrateClocksToBlueprint()) {
-                return;
-            }
             final ViewPropertyAnimator viewAnimator = mView.animate();
             viewAnimator.cancel();
             viewAnimator
@@ -3378,9 +3372,6 @@
         /** Cancels fold to AOD transition and resets view state. */
         @Override
         public void cancelFoldToAodAnimation() {
-            if (migrateClocksToBlueprint()) {
-                return;
-            }
             cancelAnimation();
             resetAlpha();
             resetTranslation();
@@ -4455,13 +4446,11 @@
                 }
             }
 
-            if (!migrateClocksToBlueprint()) {
-                mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
-                        statusBarState,
-                        keyguardFadingAway,
-                        goingToFullShade,
-                        mBarState);
-            }
+            mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+                    statusBarState,
+                    keyguardFadingAway,
+                    goingToFullShade,
+                    mBarState);
 
             if (!keyguardBottomAreaRefactor()) {
                 setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 8f9cef3..f7fed53 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -60,7 +60,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.scene.shared.flag.SceneContainerFlags;
 import com.android.systemui.scene.ui.view.WindowRootViewComponent;
 import com.android.systemui.settings.UserTracker;
@@ -507,7 +506,7 @@
 
     private void applyFitsSystemWindows(NotificationShadeWindowState state) {
         boolean fitsSystemWindows = !state.isKeyguardShowingAndNotOccluded();
-        if (!SceneContainerFlag.isEnabled() && mWindowRootView != null
+        if (mWindowRootView != null
                 && mWindowRootView.getFitsSystemWindows() != fitsSystemWindows) {
             mWindowRootView.setFitsSystemWindows(fitsSystemWindows);
             mWindowRootView.requestApplyInsets();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
index 9445d56..ea3036e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
@@ -31,6 +31,7 @@
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.time.SystemClock;
 
 import javax.inject.Inject;
 
@@ -46,9 +47,14 @@
     private NotificationEntry mEntry;
     private boolean mCancelled;
     private Throwable mInflateOrigin;
+    private final SystemClock mSystemClock;
+    private final RowInflaterTaskLogger mLogger;
+    private long mInflateStartTimeMs;
 
     @Inject
-    public RowInflaterTask() {
+    public RowInflaterTask(SystemClock systemClock, RowInflaterTaskLogger logger) {
+        mSystemClock = systemClock;
+        mLogger = logger;
     }
 
     /**
@@ -61,29 +67,49 @@
         }
         mListener = listener;
         AsyncLayoutInflater inflater = com.android.systemui.Flags.notificationRowUserContext()
-                ? new AsyncLayoutInflater(context, new RowAsyncLayoutInflater(entry))
+                ? new AsyncLayoutInflater(context, makeRowInflater(entry))
                 : new AsyncLayoutInflater(context);
         mEntry = entry;
         entry.setInflationTask(this);
+
+        mLogger.logInflateStart(entry);
+        mInflateStartTimeMs = mSystemClock.elapsedRealtime();
         inflater.inflate(R.layout.status_bar_notification_row, parent, this);
     }
 
+    private RowAsyncLayoutInflater makeRowInflater(NotificationEntry entry) {
+        return new RowAsyncLayoutInflater(entry, mSystemClock, mLogger);
+    }
+
     @VisibleForTesting
     static class RowAsyncLayoutInflater implements AsyncLayoutFactory {
         private final NotificationEntry mEntry;
+        private final SystemClock mSystemClock;
+        private final RowInflaterTaskLogger mLogger;
 
-        RowAsyncLayoutInflater(NotificationEntry entry) {
+        RowAsyncLayoutInflater(NotificationEntry entry, SystemClock systemClock,
+                RowInflaterTaskLogger logger) {
             mEntry = entry;
+            mSystemClock = systemClock;
+            mLogger = logger;
         }
 
         @Nullable
         @Override
         public View onCreateView(@Nullable View parent, @NonNull String name,
                 @NonNull Context context, @NonNull AttributeSet attrs) {
-            if (name.equals(ExpandableNotificationRow.class.getName())) {
-                return new ExpandableNotificationRow(context, attrs, mEntry);
+            if (!name.equals(ExpandableNotificationRow.class.getName())) {
+                return null;
             }
-            return null;
+
+            final long startMs = mSystemClock.elapsedRealtime();
+            final ExpandableNotificationRow row =
+                    new ExpandableNotificationRow(context, attrs, mEntry);
+            final long elapsedMs = mSystemClock.elapsedRealtime() - startMs;
+
+            mLogger.logCreatedRow(mEntry, elapsedMs);
+
+            return row;
         }
 
         @Nullable
@@ -101,6 +127,9 @@
 
     @Override
     public void onInflateFinished(View view, int resid, ViewGroup parent) {
+        final long elapsedMs = mSystemClock.elapsedRealtime() - mInflateStartTimeMs;
+        mLogger.logInflateFinish(mEntry, elapsedMs, mCancelled);
+
         if (!mCancelled) {
             try {
                 mEntry.onInflationTaskFinished();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt
new file mode 100644
index 0000000..94da6be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.NotifInflationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import javax.inject.Inject
+
+class RowInflaterTaskLogger @Inject constructor(@NotifInflationLog private val buffer: LogBuffer) {
+    fun logInflateStart(entry: NotificationEntry) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { str1 = entry.logKey },
+            { "started row inflation for $str1" }
+        )
+    }
+
+    fun logCreatedRow(entry: NotificationEntry, elapsedMs: Long) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = entry.logKey
+                long1 = elapsedMs
+            },
+            { "created row in $long1 ms for $str1" }
+        )
+    }
+
+    fun logInflateFinish(entry: NotificationEntry, elapsedMs: Long, cancelled: Boolean) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = entry.logKey
+                long1 = elapsedMs
+                bool1 = cancelled
+            },
+            { "finished ${if (bool1) "cancelled " else ""}row inflation in $long1 ms for $str1" }
+        )
+    }
+}
+
+private const val TAG = "RowInflaterTask"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index d1055c7..ee84434 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -81,6 +81,9 @@
 import com.android.systemui.navigationbar.TaskbarDelegate;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.ShadeExpansionListener;
@@ -157,6 +160,7 @@
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
     private final BouncerView mPrimaryBouncerView;
     private final Lazy<ShadeController> mShadeController;
+    private final Lazy<SceneInteractor> mSceneInteractorLazy;
 
     // Local cache of expansion events, to avoid duplicates
     private float mFraction = -1f;
@@ -381,7 +385,8 @@
             Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy,
             SelectedUserInteractor selectedUserInteractor,
             Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor,
-            JavaAdapter javaAdapter
+            JavaAdapter javaAdapter,
+            Lazy<SceneInteractor> sceneInteractorLazy
     ) {
         mContext = context;
         mViewMediatorCallback = callback;
@@ -415,6 +420,7 @@
         mSelectedUserInteractor = selectedUserInteractor;
         mSurfaceBehindInteractor = surfaceBehindInteractor;
         mJavaAdapter = javaAdapter;
+        mSceneInteractorLazy = sceneInteractorLazy;
     }
 
     KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -633,8 +639,11 @@
     public void show(Bundle options) {
         Trace.beginSection("StatusBarKeyguardViewManager#show");
         mNotificationShadeWindowController.setKeyguardShowing(true);
-        mKeyguardStateController.notifyKeyguardState(true,
-                mKeyguardStateController.isOccluded());
+        if (SceneContainerFlag.isEnabled()) {
+            mSceneInteractorLazy.get().changeScene(
+                    Scenes.Lockscreen, "StatusBarKeyguardViewManager.show");
+        }
+        mKeyguardStateController.notifyKeyguardState(true, mKeyguardStateController.isOccluded());
         reset(true /* hideBouncerWhenShowing */);
         SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
                 SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
index d19a336..5c53ff9 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/FoldLightRevealOverlayAnimation.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.unfold
 
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
 import android.animation.ValueAnimator
 import android.annotation.BinderThread
 import android.content.Context
@@ -25,6 +23,7 @@
 import android.os.SystemProperties
 import android.util.Log
 import android.view.animation.DecelerateInterpolator
+import androidx.core.animation.addListener
 import com.android.internal.foldables.FoldLockSettingAvailabilityProvider
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.display.data.repository.DeviceStateRepository
@@ -37,25 +36,17 @@
 import com.android.systemui.unfold.dagger.UnfoldBg
 import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
 import javax.inject.Inject
-import kotlin.coroutines.resume
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.TimeoutCancellationException
 import kotlinx.coroutines.android.asCoroutineDispatcher
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onCompletion
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withTimeout
 
-@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
 class FoldLightRevealOverlayAnimation
 @Inject
 constructor(
@@ -70,9 +61,6 @@
 
     private val revealProgressValueAnimator: ValueAnimator =
         ValueAnimator.ofFloat(ALPHA_OPAQUE, ALPHA_TRANSPARENT)
-    private val areAnimationEnabled: Flow<Boolean>
-        get() = animationStatusRepository.areAnimationsEnabled()
-
     private lateinit var controller: FullscreenLightRevealAnimationController
     @Volatile private var readyCallback: CompletableDeferred<Runnable>? = null
 
@@ -101,30 +89,33 @@
 
         applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
             deviceStateRepository.state
-                .map { it == DeviceStateRepository.DeviceState.FOLDED }
+                .map { it != DeviceStateRepository.DeviceState.FOLDED }
                 .distinctUntilChanged()
-                .flatMapLatest { isFolded ->
-                    flow<Nothing> {
-                            if (!areAnimationEnabled.first() || !isFolded) {
-                                return@flow
-                            }
-                            withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
-                                readyCallback = CompletableDeferred()
-                                val onReady = readyCallback?.await()
-                                readyCallback = null
-                                controller.addOverlay(ALPHA_OPAQUE, onReady)
-                                waitForScreenTurnedOn()
-                            }
+                .filter { isUnfolded -> isUnfolded }
+                .collect { controller.ensureOverlayRemoved() }
+        }
+
+        applicationScope.launch(bgHandler.asCoroutineDispatcher()) {
+            deviceStateRepository.state
+                .filter {
+                    animationStatusRepository.areAnimationsEnabled().first() &&
+                        it == DeviceStateRepository.DeviceState.FOLDED
+                }
+                .collect {
+                    try {
+                        withTimeout(WAIT_FOR_ANIMATION_TIMEOUT_MS) {
+                            readyCallback = CompletableDeferred()
+                            val onReady = readyCallback?.await()
+                            readyCallback = null
+                            controller.addOverlay(ALPHA_OPAQUE, onReady)
+                            waitForScreenTurnedOn()
                             playFoldLightRevealOverlayAnimation()
                         }
-                        .catchTimeoutAndLog()
-                        .onCompletion {
-                            val onReady = readyCallback?.takeIf { it.isCompleted }?.getCompleted()
-                            onReady?.run()
-                            readyCallback = null
-                        }
+                    } catch (e: TimeoutCancellationException) {
+                        Log.e(TAG, "Fold light reveal animation timed out")
+                        ensureOverlayRemovedInternal()
+                    }
                 }
-                .collect {}
         }
     }
 
@@ -137,34 +128,19 @@
         powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
     }
 
-    private suspend fun playFoldLightRevealOverlayAnimation() {
+    private fun ensureOverlayRemovedInternal() {
+        revealProgressValueAnimator.cancel()
+        controller.ensureOverlayRemoved()
+    }
+
+    private fun playFoldLightRevealOverlayAnimation() {
         revealProgressValueAnimator.duration = ANIMATION_DURATION
         revealProgressValueAnimator.interpolator = DecelerateInterpolator()
         revealProgressValueAnimator.addUpdateListener { animation ->
             controller.updateRevealAmount(animation.animatedFraction)
         }
-        revealProgressValueAnimator.startAndAwaitCompletion()
-    }
-
-    private suspend fun ValueAnimator.startAndAwaitCompletion(): Unit =
-        suspendCancellableCoroutine { continuation ->
-            val listener =
-                object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator) {
-                        continuation.resume(Unit)
-                        removeListener(this)
-                    }
-                }
-            addListener(listener)
-            continuation.invokeOnCancellation { removeListener(listener) }
-            start()
-        }
-
-    private fun <T> Flow<T>.catchTimeoutAndLog() = catch { exception ->
-        when (exception) {
-            is TimeoutCancellationException -> Log.e(TAG, "Fold light reveal animation timed out")
-            else -> throw exception
-        }
+        revealProgressValueAnimator.addListener(onEnd = { controller.ensureOverlayRemoved() })
+        revealProgressValueAnimator.start()
     }
 
     private companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
index 170b32c..2ab5998 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
@@ -16,14 +16,11 @@
 
 package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
 
-import android.content.Intent
-import android.provider.Settings
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.media.dialog.MediaOutputDialogFactory
-import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import javax.inject.Inject
@@ -34,17 +31,8 @@
 @Inject
 constructor(
     private val mediaOutputDialogFactory: MediaOutputDialogFactory,
-    private val activityStarter: ActivityStarter,
 ) {
 
-    fun onDeviceClick(expandable: Expandable) {
-        activityStarter.startActivity(
-            Intent(Settings.ACTION_BLUETOOTH_SETTINGS),
-            true,
-            expandable.activityTransitionController(),
-        )
-    }
-
     fun onBarClick(session: MediaDeviceSession, expandable: Expandable) {
         when (session) {
             is MediaDeviceSession.Active -> {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
index e518ed0..37bf661 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
@@ -26,13 +26,13 @@
     val iconColor: Color
     val backgroundColor: Color
 
-    class IsPlaying(
+    data class IsPlaying(
         override val icon: Icon,
         override val iconColor: Color,
         override val backgroundColor: Color,
     ) : DeviceIconViewModel
 
-    class IsNotPlaying(
+    data class IsNotPlaying(
         override val icon: Icon,
         override val iconColor: Color,
         override val backgroundColor: Color,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 85d6c9e..37661b5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -113,11 +113,6 @@
     private fun MediaDeviceSession.isPlaying(): Boolean =
         this is MediaDeviceSession.Active && playbackState?.isActive == true
 
-    fun onDeviceClick(expandable: Expandable) {
-        actionsInteractor.onDeviceClick(expandable)
-        volumePanelViewModel.dismissPanel()
-    }
-
     fun onBarClick(expandable: Expandable) {
         actionsInteractor.onBarClick(mediaDeviceSession.value, expandable)
         volumePanelViewModel.dismissPanel()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 90587d7..13fb42c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -30,6 +32,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.power.data.repository.FakePowerRepository;
 import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
 import com.android.systemui.res.R;
@@ -59,6 +62,7 @@
     @Mock protected KeyguardStatusViewController mControllerMock;
     @Mock protected InteractionJankMonitor mInteractionJankMonitor;
     @Mock protected ViewTreeObserver mViewTreeObserver;
+    @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     @Mock protected DumpManager mDumpManager;
     protected FakeKeyguardRepository mFakeKeyguardRepository;
     protected FakePowerRepository mFakePowerRepository;
@@ -89,6 +93,7 @@
                 mKeyguardLogger,
                 mInteractionJankMonitor,
                 deps.getKeyguardInteractor(),
+                mKeyguardTransitionInteractor,
                 mDumpManager,
                 PowerInteractorFactory.create(
                         mFakePowerRepository
@@ -105,6 +110,7 @@
 
         when(mKeyguardStatusView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
         when(mKeyguardClockSwitchController.getView()).thenReturn(mKeyguardClockSwitch);
+        when(mKeyguardTransitionInteractor.getGoneToAodTransition()).thenReturn(emptyFlow());
         when(mKeyguardStatusView.findViewById(R.id.keyguard_status_area))
                 .thenReturn(mKeyguardStatusAreaView);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
index d0b1dd5..df52265 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
@@ -122,13 +122,7 @@
         testScope.runTest {
             whenever(burnInHelperWrapper.burnInScale()).thenReturn(0.5f)
 
-            val burnInModel by
-                collectLastValue(
-                    underTest.burnIn(
-                        xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
-                        yDimenResourceId = R.dimen.burn_in_prevention_offset_y
-                    )
-                )
+            val burnInModel by collectLastValue(underTest.keyguardBurnIn)
 
             // After time tick, returns the configured values
             fakeKeyguardRepository.dozeTimeTick(10)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index eae0467..c65a9ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -654,6 +654,30 @@
             coroutineContext.cancelChildren()
         }
 
+    @Test
+    fun dozingToPrimaryBouncer() =
+        testScope.runTest {
+            // GIVEN a prior transition has run to DOZING
+            runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DOZING)
+            runCurrent()
+
+            // WHEN awaked by a request to show the primary bouncer, as can happen if SPFS is
+            // touched after boot
+            powerInteractor.setAwakeForTest()
+            bouncerRepository.setPrimaryShow(true)
+            advanceTimeBy(60L)
+
+            assertThat(transitionRepository)
+                .startedTransition(
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    from = KeyguardState.DOZING,
+                    ownerName = "FromDozingTransitionInteractor",
+                    animatorAssertion = { it.isNotNull() }
+                )
+
+            coroutineContext.cancelChildren()
+        }
+
     /** This handles security method NONE and screen off with lock timeout */
     @Test
     fun dozingToGoneWithKeyguardNotShowing() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
index 02bd810..4bb0d47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
@@ -27,11 +27,14 @@
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel.Companion.UNLOCKED_DELAY_MS
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.runner.RunWith
@@ -71,7 +74,10 @@
     fun isLongPressEnabled_unlocked() =
         testScope.runTest {
             val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
+            fingerprintPropertyRepository.supportsUdfps()
             keyguardRepository.setKeyguardDismissible(true)
+            advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
+            runCurrent()
             assertThat(isLongPressEnabled).isTrue()
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
new file mode 100644
index 0000000..864acfb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
+
+    @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
+    @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
+
+    private lateinit var underTest: KeyguardIndicationAreaViewModel
+    private lateinit var repository: FakeKeyguardRepository
+
+    private val startButtonFlow =
+        MutableStateFlow<KeyguardQuickAffordanceViewModel>(
+            KeyguardQuickAffordanceViewModel(
+                slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
+            )
+        )
+    private val endButtonFlow =
+        MutableStateFlow<KeyguardQuickAffordanceViewModel>(
+            KeyguardQuickAffordanceViewModel(
+                slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
+            )
+        )
+    private val alphaFlow = MutableStateFlow<Float>(1f)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
+
+        whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
+            .thenReturn(RETURNED_BURN_IN_OFFSET)
+
+        val withDeps = KeyguardInteractorFactory.create()
+        val keyguardInteractor = withDeps.keyguardInteractor
+        repository = withDeps.repository
+
+        val bottomAreaViewModel: KeyguardBottomAreaViewModel = mock()
+        whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow)
+        whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow)
+        whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow)
+        underTest =
+            KeyguardIndicationAreaViewModel(
+                keyguardInteractor = keyguardInteractor,
+                bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
+                keyguardBottomAreaViewModel = bottomAreaViewModel,
+                burnInHelperWrapper = burnInHelperWrapper,
+                shortcutsCombinedViewModel = shortcutsCombinedViewModel,
+                configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
+            )
+    }
+
+    @Test
+    fun alpha() = runTest {
+        val value = collectLastValue(underTest.alpha)
+
+        assertThat(value()).isEqualTo(1f)
+        alphaFlow.value = 0.1f
+        assertThat(value()).isEqualTo(0.1f)
+        alphaFlow.value = 0.5f
+        assertThat(value()).isEqualTo(0.5f)
+        alphaFlow.value = 0.2f
+        assertThat(value()).isEqualTo(0.2f)
+        alphaFlow.value = 0f
+        assertThat(value()).isEqualTo(0f)
+    }
+
+    @Test
+    fun isIndicationAreaPadded() = runTest {
+        repository.setKeyguardShowing(true)
+        val value = collectLastValue(underTest.isIndicationAreaPadded)
+
+        assertThat(value()).isFalse()
+        startButtonFlow.value = startButtonFlow.value.copy(isVisible = true)
+        assertThat(value()).isTrue()
+        endButtonFlow.value = endButtonFlow.value.copy(isVisible = true)
+        assertThat(value()).isTrue()
+        startButtonFlow.value = startButtonFlow.value.copy(isVisible = false)
+        assertThat(value()).isTrue()
+        endButtonFlow.value = endButtonFlow.value.copy(isVisible = false)
+        assertThat(value()).isFalse()
+    }
+
+    @Test
+    fun indicationAreaTranslationX() = runTest {
+        val value = collectLastValue(underTest.indicationAreaTranslationX)
+
+        assertThat(value()).isEqualTo(0f)
+        repository.setClockPosition(100, 100)
+        assertThat(value()).isEqualTo(100f)
+        repository.setClockPosition(200, 100)
+        assertThat(value()).isEqualTo(200f)
+        repository.setClockPosition(200, 200)
+        assertThat(value()).isEqualTo(200f)
+        repository.setClockPosition(300, 100)
+        assertThat(value()).isEqualTo(300f)
+    }
+
+    @Test
+    fun indicationAreaTranslationY() = runTest {
+        val value = collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
+
+        // Negative 0 - apparently there's a difference in floating point arithmetic - FML
+        assertThat(value()).isEqualTo(-0f)
+        val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
+        assertThat(value()).isEqualTo(expected1)
+        val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
+        assertThat(value()).isEqualTo(expected2)
+        val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
+        assertThat(value()).isEqualTo(expected3)
+        val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
+        assertThat(value()).isEqualTo(expected4)
+    }
+
+    private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
+        repository.setDozeAmount(dozeAmount)
+        return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
+    }
+
+    companion object {
+        private const val DEFAULT_BURN_IN_OFFSET = 5
+        private const val RETURNED_BURN_IN_OFFSET = 3
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
index 2f911fff..92c2404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
@@ -22,8 +22,10 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import java.lang.IllegalStateException
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -31,12 +33,14 @@
 import org.mockito.Mockito.verify
 
 @SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
 class ScreenshotSoundControllerTest : SysuiTestCase() {
 
     private val soundProvider = mock<ScreenshotSoundProvider>()
     private val mediaPlayer = mock<MediaPlayer>()
     private val bgDispatcher = UnconfinedTestDispatcher()
     private val scope = TestScope(bgDispatcher)
+
     @Before
     fun setup() {
         whenever(soundProvider.getScreenshotSound()).thenReturn(mediaPlayer)
@@ -45,52 +49,59 @@
     @Test
     fun init_soundLoading() {
         createController()
-        bgDispatcher.scheduler.runCurrent()
+        scope.advanceUntilIdle()
 
         verify(soundProvider).getScreenshotSound()
     }
 
     @Test
-    fun init_soundLoadingException_playAndReleaseDoNotThrow() = runTest {
-        whenever(soundProvider.getScreenshotSound()).thenThrow(IllegalStateException())
+    fun init_soundLoadingException_playAndReleaseDoNotThrow() =
+        scope.runTest {
+            whenever(soundProvider.getScreenshotSound()).thenThrow(IllegalStateException())
 
-        val controller = createController()
+            val controller = createController()
 
-        controller.playCameraSound().await()
-        controller.releaseScreenshotSound().await()
+            controller.playScreenshotSound()
+            advanceUntilIdle()
 
-        verify(mediaPlayer, never()).start()
-        verify(mediaPlayer, never()).release()
-    }
+            verify(mediaPlayer, never()).start()
+            verify(mediaPlayer, never()).release()
+        }
 
     @Test
-    fun playCameraSound_soundLoadingSuccessful_mediaPlayerPlays() = runTest {
-        val controller = createController()
+    fun playCameraSound_soundLoadingSuccessful_mediaPlayerPlays() =
+        scope.runTest {
+            val controller = createController()
 
-        controller.playCameraSound().await()
+            controller.playScreenshotSound()
+            advanceUntilIdle()
 
-        verify(mediaPlayer).start()
-    }
+            verify(mediaPlayer).start()
+        }
 
     @Test
-    fun playCameraSound_illegalStateException_doesNotThrow() = runTest {
-        whenever(mediaPlayer.start()).thenThrow(IllegalStateException())
+    fun playCameraSound_illegalStateException_doesNotThrow() =
+        scope.runTest {
+            whenever(mediaPlayer.start()).thenThrow(IllegalStateException())
 
-        val controller = createController()
-        controller.playCameraSound().await()
+            val controller = createController()
+            controller.playScreenshotSound()
+            advanceUntilIdle()
 
-        verify(mediaPlayer).start()
-        verify(mediaPlayer).release()
-    }
+            verify(mediaPlayer).start()
+            verify(mediaPlayer).release()
+        }
 
     @Test
-    fun playCameraSound_soundLoadingSuccessful_mediaPlayerReleases() = runTest {
-        val controller = createController()
+    fun playCameraSound_soundLoadingSuccessful_mediaPlayerReleases() =
+        scope.runTest {
+            val controller = createController()
 
-        controller.releaseScreenshotSound().await()
+            controller.releaseScreenshotSound()
+            advanceUntilIdle()
 
-        verify(mediaPlayer).release()
-    }
+            verify(mediaPlayer).release()
+        }
 
     private fun createController() =
         ScreenshotSoundControllerImpl(soundProvider, scope, bgDispatcher)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index f8771b2..fd7b139 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -461,6 +461,7 @@
                 mKeyguardLogger,
                 mInteractionJankMonitor,
                 mKeyguardInteractor,
+                mKeyguardTransitionInteractor,
                 mDumpManager,
                 mPowerInteractor));
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index e78081f..fb49499f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -89,6 +89,8 @@
 import com.android.systemui.statusbar.policy.SmartReplyConstants;
 import com.android.systemui.statusbar.policy.SmartReplyStateInflater;
 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
+import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.util.time.SystemClockImpl;
 import com.android.systemui.wmshell.BubblesManager;
 import com.android.systemui.wmshell.BubblesTestActivity;
 
@@ -136,6 +138,8 @@
     public final Runnable mFutureDismissalRunnable;
     private @InflationFlag int mDefaultInflationFlags;
     private final FakeFeatureFlags mFeatureFlags;
+    private final SystemClock mSystemClock;
+    private final RowInflaterTaskLogger mRowInflaterTaskLogger;
 
     public NotificationTestHelper(
             Context context,
@@ -199,6 +203,9 @@
         mFutureDismissalRunnable = mock(Runnable.class);
         when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt()))
                 .thenReturn(mFutureDismissalRunnable);
+
+        mSystemClock = new SystemClockImpl();
+        mRowInflaterTaskLogger = mock(RowInflaterTaskLogger.class);
     }
 
     public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) {
@@ -572,7 +579,8 @@
         LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
                 Context.LAYOUT_INFLATER_SERVICE);
         if (com.android.systemui.Flags.notificationRowUserContext()) {
-            inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry));
+            inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry, mSystemClock,
+                    mRowInflaterTaskLogger));
         }
         mRow = (ExpandableNotificationRow) inflater.inflate(
                 R.layout.status_bar_notification_row,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 3666248..f050857 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -86,6 +86,7 @@
 import com.android.systemui.navigationbar.TaskbarDelegate;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.shade.NotificationShadeWindowView;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -224,7 +225,8 @@
                         () -> mock(KeyguardDismissActionInteractor.class),
                         mSelectedUserInteractor,
                         () -> mock(KeyguardSurfaceBehindInteractor.class),
-                        mock(JavaAdapter.class)) {
+                        mock(JavaAdapter.class),
+                        () -> mock(SceneInteractor.class)) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
@@ -733,7 +735,8 @@
                         () -> mock(KeyguardDismissActionInteractor.class),
                         mSelectedUserInteractor,
                         () -> mock(KeyguardSurfaceBehindInteractor.class),
-                        mock(JavaAdapter.class)) {
+                        mock(JavaAdapter.class),
+                        () -> mock(SceneInteractor.class)) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 1e305d6..793e2d7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -18,6 +18,7 @@
 package com.android.systemui.keyguard.data.repository
 
 import android.graphics.Point
+import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -57,6 +58,9 @@
     private val _bottomAreaAlpha = MutableStateFlow(1f)
     override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha
 
+    private val _clockPosition = MutableStateFlow(Position(0, 0))
+    override val clockPosition: StateFlow<Position> = _clockPosition
+
     private val _isKeyguardShowing = MutableStateFlow(false)
     override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
 
@@ -145,6 +149,10 @@
         _bottomAreaAlpha.value = alpha
     }
 
+    override fun setClockPosition(x: Int, y: Int) {
+        _clockPosition.value = Position(x, y)
+    }
+
     fun setKeyguardShowing(isShowing: Boolean) {
         _isKeyguardShowing.value = isShowing
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index d5d357f..c2300a1e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -43,6 +43,7 @@
         aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
         dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel,
         dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
+        dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
         glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
         goneToAodTransitionViewModel = goneToAodTransitionViewModel,
         goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index 3f20df3..a3b1a0e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -43,7 +42,7 @@
     Kosmos.Fixture { FakeLocalMediaRepositoryFactory { localMediaRepository } }
 
 val Kosmos.mediaOutputActionsInteractor by
-    Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+    Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory) }
 val Kosmos.mediaControllerRepository by Kosmos.Fixture { FakeMediaControllerRepository() }
 val Kosmos.mediaOutputInteractor by
     Kosmos.Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
deleted file mode 100644
index ad8ccb0..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.panel
-
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
-
-val Kosmos.mediaOutputActionsInteractor by
-    Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
index cc94090..9057d16 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
@@ -42,7 +42,6 @@
     public static final String KEYBOARD_PATHS = "keyboard_paths";
     public static final String GRAPHICS_NATIVE_CLASSES = "graphics_native_classes";
 
-    public static final String VALUE_N_A = "**n/a**";
     public static final String LIBANDROID_RUNTIME_NAME = "android_runtime";
 
     private static String sInitialDir = new File("").getAbsolutePath();
@@ -130,8 +129,6 @@
         }
         setProperty(CORE_NATIVE_CLASSES, jniClasses);
         setProperty(GRAPHICS_NATIVE_CLASSES, "");
-        setProperty(ICU_DATA_PATH, VALUE_N_A);
-        setProperty(KEYBOARD_PATHS, VALUE_N_A);
 
         RavenwoodUtils.loadJniLibrary(LIBANDROID_RUNTIME_NAME);
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index f4ea754..279bd72 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -1004,17 +1004,19 @@
                                     && overscrollState(event, mFirstPointerDownLocation)
                                     == OVERSCROLL_VERTICAL_EDGE) {
                                 transitionToDelegatingStateAndClear();
-                            } // TODO(b/319537921): should there be an else here?
-                            //Primary pointer is swiping, so transit to PanningScalingState
-                            transitToPanningScalingStateAndClear();
+                            } else {
+                                //Primary pointer is swiping, so transit to PanningScalingState
+                                transitToPanningScalingStateAndClear();
+                            }
                         } else if (mIsSinglePanningEnabled
                                 && isActivated()
                                 && event.getPointerCount() == 1) {
                             if (overscrollState(event, mFirstPointerDownLocation)
                                     == OVERSCROLL_VERTICAL_EDGE) {
                                 transitionToDelegatingStateAndClear();
-                            } // TODO(b/319537921): should there be an else here?
-                            transitToSinglePanningStateAndClear();
+                            } else {
+                                transitToSinglePanningStateAndClear();
+                            }
                         } else if (!mIsTwoFingerCountReached) {
                             // If it is a two-finger gesture, do not transition to the
                             // delegating state to ensure the reachability of
@@ -1257,17 +1259,19 @@
                                     && overscrollState(event, mFirstPointerDownLocation)
                                     == OVERSCROLL_VERTICAL_EDGE) {
                                 transitionToDelegatingStateAndClear();
+                            } else {
+                                //Primary pointer is swiping, so transit to PanningScalingState
+                                transitToPanningScalingStateAndClear();
                             }
-                            //Primary pointer is swiping, so transit to PanningScalingState
-                            transitToPanningScalingStateAndClear();
                         } else if (mIsSinglePanningEnabled
                                 && isActivated()
                                 && event.getPointerCount() == 1) {
                             if (overscrollState(event, mFirstPointerDownLocation)
                                     == OVERSCROLL_VERTICAL_EDGE) {
                                 transitionToDelegatingStateAndClear();
+                            } else {
+                                transitToSinglePanningStateAndClear();
                             }
-                            transitToSinglePanningStateAndClear();
                         } else {
                             transitionToDelegatingStateAndClear();
                         }
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 6f45f60..29b9d44 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -476,8 +476,12 @@
             } else {
                 // If the package is being updated, we'll receive a PACKAGE_ADDED
                 // shortly, otherwise it is removed permanently.
-                final boolean packageRemovedPermanently = (extras == null
-                        || !extras.getBoolean(Intent.EXTRA_REPLACING, false));
+                boolean isReplacing = extras != null && extras.getBoolean(Intent.EXTRA_REPLACING,
+                        false);
+                boolean isArchival = extras != null && extras.getBoolean(Intent.EXTRA_ARCHIVAL,
+                        false);
+                final boolean packageRemovedPermanently =
+                        (extras == null || !isReplacing || (isReplacing && isArchival));
 
                 if (packageRemovedPermanently) {
                     for (String pkgName : pkgList) {
@@ -2074,6 +2078,7 @@
     private void handleNotifyAppWidgetViewDataChanged(Host host, IAppWidgetHost callbacks,
             int appWidgetId, int viewId, long requestId) {
         try {
+            Slog.d(TAG, "Trying to notify widget view data changed");
             callbacks.viewDataChanged(appWidgetId, viewId);
             host.lastWidgetUpdateSequenceNo = requestId;
         } catch (RemoteException re) {
@@ -2158,6 +2163,9 @@
     private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,
             int appWidgetId, RemoteViews views, long requestId) {
         try {
+            Slog.d(TAG, "Trying to notify widget update for package "
+                    + (views == null ? "null" : views.getPackage())
+                    + " with widget id: " + appWidgetId);
             callbacks.updateAppWidget(appWidgetId, views);
             host.lastWidgetUpdateSequenceNo = requestId;
         } catch (RemoteException re) {
@@ -2196,6 +2204,7 @@
     private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks,
             int appWidgetId, AppWidgetProviderInfo info, long requestId) {
         try {
+            Slog.d(TAG, "Trying to notify provider update");
             callbacks.providerChanged(appWidgetId, info);
             host.lastWidgetUpdateSequenceNo = requestId;
         } catch (RemoteException re) {
@@ -2239,6 +2248,7 @@
     private void handleNotifyAppWidgetRemoved(Host host, IAppWidgetHost callbacks, int appWidgetId,
             long requestId) {
         try {
+            Slog.d(TAG, "Trying to notify widget removed");
             callbacks.appWidgetRemoved(appWidgetId);
             host.lastWidgetUpdateSequenceNo = requestId;
         } catch (RemoteException re) {
@@ -2286,6 +2296,7 @@
 
     private void handleNotifyProvidersChanged(Host host, IAppWidgetHost callbacks) {
         try {
+            Slog.d(TAG, "Trying to notify widget providers changed");
             callbacks.providersChanged();
         } catch (RemoteException re) {
             synchronized (mLock) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index a478a3d..17ba073 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -228,8 +228,6 @@
                 /* cdmService */ this, mAssociationStore, mPersistentStore,
                 mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
 
-        loadAssociationsFromDisk();
-
         mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
 
         mAssociationStore.registerListener(mAssociationStoreChangeListener);
@@ -240,13 +238,18 @@
         mCompanionAppController = new CompanionApplicationController(
                 context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor,
                 mPowerManagerInternal);
+
+        mAssociationRevokeProcessor = new AssociationRevokeProcessor(this, mAssociationStore,
+                mPackageManagerInternal, mDevicePresenceMonitor, mCompanionAppController,
+                mSystemDataTransferRequestStore);
+
+        loadAssociationsFromDisk();
+
         mTransportManager = new CompanionTransportManager(context, mAssociationStore);
         mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
                 mPackageManagerInternal, mAssociationStore,
                 mSystemDataTransferRequestStore, mTransportManager);
-        mAssociationRevokeProcessor = new AssociationRevokeProcessor(this, mAssociationStore,
-                mPackageManagerInternal, mDevicePresenceMonitor, mCompanionAppController,
-                mSystemDataTransferRequestStore);
+
         // TODO(b/279663946): move context sync to a dedicated system service
         mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager);
 
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 9189ea7..e1d7be1 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -348,6 +348,9 @@
     // marked as stopped by the system
     @NonNull private final Set<String> mInitialNonStoppedSystemPackages = new ArraySet<>();
 
+    // Which packages (key) are allowed to join particular SharedUid (value).
+    @NonNull private final Map<String, String> mPackageToSharedUidAllowList = new ArrayMap<>();
+
     // A map of preloaded package names and the path to its app metadata file path.
     private final ArrayMap<String, String> mAppMetadataFilePaths = new ArrayMap<>();
 
@@ -567,6 +570,11 @@
         return mInitialNonStoppedSystemPackages;
     }
 
+    @NonNull
+    public Map<String, String> getPackageToSharedUidAllowList() {
+        return mPackageToSharedUidAllowList;
+    }
+
     public ArrayMap<String, String> getAppMetadataFilePaths() {
         return mAppMetadataFilePaths;
     }
@@ -1563,6 +1571,19 @@
                             mInitialNonStoppedSystemPackages.add(pkgName);
                         }
                     } break;
+                    case "allow-package-shareduid": {
+                        String pkgName = parser.getAttributeValue(null, "package");
+                        String sharedUid = parser.getAttributeValue(null, "shareduid");
+                        if (TextUtils.isEmpty(pkgName)) {
+                            Slog.w(TAG, "<" + name + "> without package in " + permFile
+                                    + " at " + parser.getPositionDescription());
+                        } else if (TextUtils.isEmpty(sharedUid)) {
+                            Slog.w(TAG, "<" + name + "> without shareduid in " + permFile
+                                    + " at " + parser.getPositionDescription());
+                        } else {
+                            mPackageToSharedUidAllowList.put(pkgName, sharedUid);
+                        }
+                    }
                     case "asl-file": {
                         String packageName = parser.getAttributeValue(null, "package");
                         String path = parser.getAttributeValue(null, "path");
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 5e9d1cb..8dc15ad 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -163,6 +163,9 @@
                 }
             ],
             "file_patterns": ["PinnerService\\.java"]
+        },
+        {
+            "name": "FrameworksVpnTests"
         }
     ]
 }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index b8e09cc..258f53d 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -372,6 +372,15 @@
     @Overridable
     public static final long FGS_BOOT_COMPLETED_RESTRICTIONS = 296558535L;
 
+    /**
+     * Disables foreground service background starts in System Alert Window for all types
+     * unless it already has a System Overlay Window.
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
+    @Overridable
+    public static final long FGS_SAW_RESTRICTIONS = 319471980L;
+
     final ActivityManagerService mAm;
 
     // Maximum number of services that we allow to start in the background
@@ -8526,10 +8535,31 @@
             }
         }
 
+        // The flag being enabled isn't enough to deny background start: we need to also check
+        // if there is a system alert UI present.
         if (ret == REASON_DENIED) {
-            if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
-                    callingPackage)) {
-                ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+            // Flag check: are we disabling SAW FGS background starts?
+            final boolean shouldDisableSaw = Flags.fgsDisableSaw()
+                    && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, callingUid);
+            if (shouldDisableSaw) {
+                final ProcessRecord processRecord = mAm
+                        .getProcessRecordLocked(targetService.processName,
+                                targetService.appInfo.uid);
+                if (processRecord != null) {
+                    if (processRecord.mState.hasOverlayUi()) {
+                        if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
+                                callingPackage)) {
+                            ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+                        }
+                    }
+                } else {
+                    Slog.e(TAG, "Could not find process record for SAW check");
+                }
+            } else {
+                if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
+                        callingPackage)) {
+                    ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index c06bdf9..b1823b4 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -23,6 +23,13 @@
 }
 
 flag {
+    name: "fgs_disable_saw"
+    namespace: "backstage_power"
+    description: "Disable System Alert Window FGS start"
+    bug: "296558535"
+}
+
+flag {
     name: "bfgs_managed_network_access"
     namespace: "backstage_power"
     description: "Restrict network access for certain applications in BFGS process state"
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
index d584c99..d4e46a9 100644
--- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -179,15 +179,18 @@
             nextConsumer.consume(current);
         } else if (mNextConsumer != null) {
             mNextConsumer.add(nextConsumer);
-        } else {
+        } else if (mLightSensor != null) {
             mDestroyed = false;
             mNextConsumer = nextConsumer;
             enableLightSensorLoggingLocked();
+        } else {
+            Slog.w(TAG, "No light sensor - use current to consume");
+            nextConsumer.consume(current);
         }
     }
 
     private void enableLightSensorLoggingLocked() {
-        if (!mEnabled) {
+        if (!mEnabled && mLightSensor != null) {
             mEnabled = true;
             mLastAmbientLux = -1;
             mSensorManager.registerListener(mLightSensorListener, mLightSensor,
@@ -201,7 +204,7 @@
     private void disableLightSensorLoggingLocked(boolean destroying) {
         resetTimerLocked(false /* start */);
 
-        if (mEnabled) {
+        if (mEnabled && mLightSensor != null) {
             mEnabled = false;
             if (!destroying) {
                 mLastAmbientLux = -1;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 6b8586a..68b4e3f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -119,7 +119,7 @@
      * Receives the incoming binder calls from FaceManager.
      */
     @VisibleForTesting final class FaceServiceWrapper extends IFaceService.Stub {
-        @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
                 @NonNull String opPackageName) {
diff --git a/services/core/java/com/android/server/connectivity/TEST_MAPPING b/services/core/java/com/android/server/connectivity/TEST_MAPPING
index 687d4b0..f508319 100644
--- a/services/core/java/com/android/server/connectivity/TEST_MAPPING
+++ b/services/core/java/com/android/server/connectivity/TEST_MAPPING
@@ -7,7 +7,7 @@
                   "exclude-annotation": "com.android.testutils.SkipPresubmit"
               }
           ],
-          "file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
+          "file_patterns": ["VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
       }
   ],
   "presubmit-large": [
@@ -26,5 +26,10 @@
           ],
         "file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
       }
+  ],
+  "postsubmit":[
+    {
+      "name":"FrameworksVpnTests"
+    }
   ]
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig
new file mode 100644
index 0000000..10b5eff
--- /dev/null
+++ b/services/core/java/com/android/server/flags/services.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.flags"
+
+flag {
+     namespace: "wear_frameworks"
+     name: "enable_odp_feature_guard"
+     description: "Enable guard based on system feature to prevent OnDevicePersonalization service from starting on form factors."
+     bug: "322249125"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index a79e771..05b1cb69 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -54,6 +54,7 @@
 import android.hardware.input.InputSensorInfo;
 import android.hardware.input.InputSettings;
 import android.hardware.input.KeyboardLayout;
+import android.hardware.input.KeyboardLayoutSelectionResult;
 import android.hardware.input.TouchCalibration;
 import android.hardware.lights.Light;
 import android.hardware.lights.LightState;
@@ -1244,9 +1245,9 @@
     }
 
     @Override // Binder call
-    public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
-            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @Nullable InputMethodSubtype imeSubtype) {
+    public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
+            InputDeviceIdentifier identifier, @UserIdInt int userId,
+            @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
         return mKeyboardLayoutManager.getKeyboardLayoutForInputDevice(identifier, userId,
                 imeInfo, imeSubtype);
     }
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 46668de..283e692 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -16,10 +16,11 @@
 
 package com.android.server.input;
 
-import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT;
-import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE;
-import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER;
-import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
+import static android.hardware.input.KeyboardLayoutSelectionResult.FAILED;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT;
 
 import android.annotation.AnyThread;
 import android.annotation.MainThread;
@@ -46,6 +47,7 @@
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
 import android.hardware.input.KeyboardLayout;
+import android.hardware.input.KeyboardLayoutSelectionResult;
 import android.icu.lang.UScript;
 import android.icu.util.ULocale;
 import android.os.Bundle;
@@ -79,7 +81,6 @@
 import com.android.server.LocalServices;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.input.KeyboardMetricsCollector.KeyboardConfigurationEvent;
-import com.android.server.input.KeyboardMetricsCollector.LayoutSelectionCriteria;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 
 import libcore.io.Streams;
@@ -130,7 +131,8 @@
     // This cache stores "best-matched" layouts so that we don't need to run the matching
     // algorithm repeatedly.
     @GuardedBy("mKeyboardLayoutCache")
-    private final Map<String, KeyboardLayoutInfo> mKeyboardLayoutCache = new ArrayMap<>();
+    private final Map<String, KeyboardLayoutSelectionResult> mKeyboardLayoutCache =
+            new ArrayMap<>();
     private final Object mImeInfoLock = new Object();
     @Nullable
     @GuardedBy("mImeInfoLock")
@@ -222,17 +224,17 @@
         } else {
             Set<String> selectedLayouts = new HashSet<>();
             List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping();
-            List<KeyboardLayoutInfo> layoutInfoList = new ArrayList<>();
+            List<KeyboardLayoutSelectionResult> resultList = new ArrayList<>();
             boolean hasMissingLayout = false;
             for (ImeInfo imeInfo : imeInfoList) {
                 // Check if the layout has been previously configured
-                KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
+                KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
                         keyboardIdentifier, imeInfo);
-                boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null;
+                boolean noLayoutFound = result.getLayoutDescriptor() == null;
                 if (!noLayoutFound) {
-                    selectedLayouts.add(layoutInfo.mDescriptor);
+                    selectedLayouts.add(result.getLayoutDescriptor());
                 }
-                layoutInfoList.add(layoutInfo);
+                resultList.add(result);
                 hasMissingLayout |= noLayoutFound;
             }
 
@@ -260,7 +262,7 @@
                     }
 
                     if (shouldLogConfiguration) {
-                        logKeyboardConfigurationEvent(inputDevice, imeInfoList, layoutInfoList,
+                        logKeyboardConfigurationEvent(inputDevice, imeInfoList, resultList,
                                 !mDataStore.hasInputDeviceEntry(key));
                     }
                 } finally {
@@ -757,10 +759,10 @@
         String keyboardLayoutDescriptor;
         if (useNewSettingsUi()) {
             synchronized (mImeInfoLock) {
-                KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
+                KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
                         new KeyboardIdentifier(identifier, languageTag, layoutType),
                         mCurrentImeInfo);
-                keyboardLayoutDescriptor = layoutInfo == null ? null : layoutInfo.mDescriptor;
+                keyboardLayoutDescriptor = result.getLayoutDescriptor();
             }
         } else {
             keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
@@ -788,26 +790,26 @@
     }
 
     @AnyThread
-    @Nullable
-    public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
-            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
-            @Nullable InputMethodSubtype imeSubtype) {
+    @NonNull
+    public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
+            InputDeviceIdentifier identifier, @UserIdInt int userId,
+            @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
         if (!useNewSettingsUi()) {
             Slog.e(TAG, "getKeyboardLayoutForInputDevice() API not supported");
-            return null;
+            return FAILED;
         }
         InputDevice inputDevice = getInputDevice(identifier);
         if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
-            return null;
+            return FAILED;
         }
         KeyboardIdentifier keyboardIdentifier = new KeyboardIdentifier(inputDevice);
-        KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
+        KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
                 keyboardIdentifier, new ImeInfo(userId, imeInfo, imeSubtype));
         if (DEBUG) {
             Slog.d(TAG, "getKeyboardLayoutForInputDevice() " + identifier.toString() + ", userId : "
-                    + userId + ", subtype = " + imeSubtype + " -> " + layoutInfo);
+                    + userId + ", subtype = " + imeSubtype + " -> " + result);
         }
-        return layoutInfo != null ? layoutInfo.mDescriptor : null;
+        return result;
     }
 
     @AnyThread
@@ -942,13 +944,13 @@
     }
 
     @Nullable
-    private KeyboardLayoutInfo getKeyboardLayoutForInputDeviceInternal(
+    private KeyboardLayoutSelectionResult getKeyboardLayoutForInputDeviceInternal(
             KeyboardIdentifier keyboardIdentifier, @Nullable ImeInfo imeInfo) {
         String layoutKey = new LayoutKey(keyboardIdentifier, imeInfo).toString();
         synchronized (mDataStore) {
             String layout = mDataStore.getKeyboardLayout(keyboardIdentifier.toString(), layoutKey);
             if (layout != null) {
-                return new KeyboardLayoutInfo(layout, LAYOUT_SELECTION_CRITERIA_USER);
+                return new KeyboardLayoutSelectionResult(layout, LAYOUT_SELECTION_CRITERIA_USER);
             }
         }
 
@@ -961,17 +963,17 @@
                 KeyboardLayout[] layoutList = getKeyboardLayoutListForInputDeviceInternal(
                         keyboardIdentifier, imeInfo);
                 // Call auto-matching algorithm to find the best matching layout
-                KeyboardLayoutInfo layoutInfo =
+                KeyboardLayoutSelectionResult result =
                         getDefaultKeyboardLayoutBasedOnImeInfo(keyboardIdentifier, imeInfo,
                                 layoutList);
-                mKeyboardLayoutCache.put(layoutKey, layoutInfo);
-                return layoutInfo;
+                mKeyboardLayoutCache.put(layoutKey, result);
+                return result;
             }
         }
     }
 
-    @Nullable
-    private static KeyboardLayoutInfo getDefaultKeyboardLayoutBasedOnImeInfo(
+    @NonNull
+    private static KeyboardLayoutSelectionResult getDefaultKeyboardLayoutBasedOnImeInfo(
             KeyboardIdentifier keyboardIdentifier, @Nullable ImeInfo imeInfo,
             KeyboardLayout[] layoutList) {
         Arrays.sort(layoutList);
@@ -986,7 +988,7 @@
                                     + "vendor and product Ids. " + keyboardIdentifier
                                     + " : " + layout.getDescriptor());
                 }
-                return new KeyboardLayoutInfo(layout.getDescriptor(),
+                return new KeyboardLayoutSelectionResult(layout.getDescriptor(),
                         LAYOUT_SELECTION_CRITERIA_DEVICE);
             }
         }
@@ -1004,13 +1006,14 @@
                                     + "HW information (Language tag and Layout type). "
                                     + keyboardIdentifier + " : " + layoutDesc);
                 }
-                return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_DEVICE);
+                return new KeyboardLayoutSelectionResult(layoutDesc,
+                        LAYOUT_SELECTION_CRITERIA_DEVICE);
             }
         }
 
         if (imeInfo == null || imeInfo.mImeSubtypeHandle == null || imeInfo.mImeSubtype == null) {
             // Can't auto select layout based on IME info is null
-            return null;
+            return FAILED;
         }
 
         InputMethodSubtype subtype = imeInfo.mImeSubtype;
@@ -1027,9 +1030,10 @@
                             + layoutDesc);
         }
         if (layoutDesc != null) {
-            return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD);
+            return new KeyboardLayoutSelectionResult(layoutDesc,
+                    LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD);
         }
-        return null;
+        return FAILED;
     }
 
     @Nullable
@@ -1246,21 +1250,23 @@
     }
 
     private void logKeyboardConfigurationEvent(@NonNull InputDevice inputDevice,
-            @NonNull List<ImeInfo> imeInfoList, @NonNull List<KeyboardLayoutInfo> layoutInfoList,
+            @NonNull List<ImeInfo> imeInfoList,
+            @NonNull List<KeyboardLayoutSelectionResult> resultList,
             boolean isFirstConfiguration) {
-        if (imeInfoList.isEmpty() || layoutInfoList.isEmpty()) {
+        if (imeInfoList.isEmpty() || resultList.isEmpty()) {
             return;
         }
         KeyboardConfigurationEvent.Builder configurationEventBuilder =
                 new KeyboardConfigurationEvent.Builder(inputDevice).setIsFirstTimeConfiguration(
                         isFirstConfiguration);
         for (int i = 0; i < imeInfoList.size(); i++) {
-            KeyboardLayoutInfo layoutInfo = layoutInfoList.get(i);
+            KeyboardLayoutSelectionResult result = resultList.get(i);
             String layoutName = null;
             int layoutSelectionCriteria = LAYOUT_SELECTION_CRITERIA_DEFAULT;
-            if (layoutInfo != null && layoutInfo.mDescriptor != null) {
-                layoutSelectionCriteria = layoutInfo.mSelectionCriteria;
-                KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(layoutInfo.mDescriptor);
+            if (result != null && result.getLayoutDescriptor() != null) {
+                layoutSelectionCriteria = result.getSelectionCriteria();
+                KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(
+                        result.getLayoutDescriptor());
                 if (d != null) {
                     layoutName = d.keyboardLayoutName;
                 }
@@ -1477,33 +1483,6 @@
         }
     }
 
-    private static class KeyboardLayoutInfo {
-        @Nullable
-        private final String mDescriptor;
-        @LayoutSelectionCriteria
-        private final int mSelectionCriteria;
-
-        private KeyboardLayoutInfo(@Nullable String descriptor,
-                @LayoutSelectionCriteria int selectionCriteria) {
-            mDescriptor = descriptor;
-            mSelectionCriteria = selectionCriteria;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj instanceof KeyboardLayoutInfo) {
-                return Objects.equals(mDescriptor, ((KeyboardLayoutInfo) obj).mDescriptor)
-                        && mSelectionCriteria == ((KeyboardLayoutInfo) obj).mSelectionCriteria;
-            }
-            return false;
-        }
-
-        @Override
-        public int hashCode() {
-            return 31 * mSelectionCriteria + mDescriptor.hashCode();
-        }
-    }
-
     private interface KeyboardLayoutVisitor {
         void visitKeyboardLayout(Resources resources,
                 int keyboardLayoutResId, KeyboardLayout layout);
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index f53b941..b8ae737 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -16,14 +16,18 @@
 
 package com.android.server.input;
 
-import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT;
+import static android.hardware.input.KeyboardLayoutSelectionResult.layoutSelectionCriteriaToString;
 
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.role.RoleManager;
 import android.content.Intent;
 import android.hardware.input.KeyboardLayout;
+import android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria;
 import android.icu.util.ULocale;
 import android.text.TextUtils;
 import android.util.Log;
@@ -40,7 +44,6 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.policy.ModifierShortcutManager;
 
-import java.lang.annotation.Retention;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -57,32 +60,6 @@
     // (requires restart)
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    @Retention(SOURCE)
-    @IntDef(prefix = {"LAYOUT_SELECTION_CRITERIA_"}, value = {
-            LAYOUT_SELECTION_CRITERIA_UNSPECIFIED,
-            LAYOUT_SELECTION_CRITERIA_USER,
-            LAYOUT_SELECTION_CRITERIA_DEVICE,
-            LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
-            LAYOUT_SELECTION_CRITERIA_DEFAULT
-    })
-    public @interface LayoutSelectionCriteria {
-    }
-
-    /** Unspecified layout selection criteria */
-    public static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED = 0;
-
-    /** Manual selection by user */
-    public static final int LAYOUT_SELECTION_CRITERIA_USER = 1;
-
-    /** Auto-detection based on device provided language tag and layout type */
-    public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 2;
-
-    /** Auto-detection based on IME provided language tag and layout type */
-    public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 3;
-
-    /** Default selection */
-    public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 4;
-
     @VisibleForTesting
     static final String DEFAULT_LAYOUT_NAME = "Default";
 
@@ -629,30 +606,16 @@
 
         @Override
         public String toString() {
-            return "{keyboardLanguageTag = " + keyboardLanguageTag + " keyboardLayoutType = "
+            return "{keyboardLanguageTag = " + keyboardLanguageTag
+                    + " keyboardLayoutType = "
                     + KeyboardLayout.LayoutType.getLayoutNameFromValue(keyboardLayoutType)
-                    + " keyboardLayoutName = " + keyboardLayoutName + " layoutSelectionCriteria = "
-                    + getStringForSelectionCriteria(layoutSelectionCriteria)
-                    + "imeLanguageTag = " + imeLanguageTag + " imeLayoutType = "
-                    + KeyboardLayout.LayoutType.getLayoutNameFromValue(imeLayoutType) + "}";
-        }
-    }
-
-    private static String getStringForSelectionCriteria(
-            @LayoutSelectionCriteria int layoutSelectionCriteria) {
-        switch (layoutSelectionCriteria) {
-            case LAYOUT_SELECTION_CRITERIA_UNSPECIFIED:
-                return "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED";
-            case LAYOUT_SELECTION_CRITERIA_USER:
-                return "LAYOUT_SELECTION_CRITERIA_USER";
-            case LAYOUT_SELECTION_CRITERIA_DEVICE:
-                return "LAYOUT_SELECTION_CRITERIA_DEVICE";
-            case LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD:
-                return "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD";
-            case LAYOUT_SELECTION_CRITERIA_DEFAULT:
-                return "LAYOUT_SELECTION_CRITERIA_DEFAULT";
-            default:
-                return "INVALID_CRITERIA";
+                    + " keyboardLayoutName = " + keyboardLayoutName
+                    + " layoutSelectionCriteria = "
+                    + layoutSelectionCriteriaToString(layoutSelectionCriteria)
+                    + " imeLanguageTag = " + imeLanguageTag
+                    + " imeLayoutType = " + KeyboardLayout.LayoutType.getLayoutNameFromValue(
+                    imeLayoutType)
+                    + "}";
         }
     }
 
diff --git a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
index b30f5ec..6eae9a4 100644
--- a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
+++ b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
@@ -229,7 +229,8 @@
     /** Report a key event to the debug view. */
     @AnyThread
     public void reportKeyEvent(KeyEvent event) {
-        post(() -> handleKeyEvent(KeyEvent.obtain((KeyEvent) event)));
+        KeyEvent keyEvent = KeyEvent.obtain(event);
+        post(() -> handleKeyEvent(keyEvent));
     }
 
     /** Report a motion event to the debug view. */
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index 8826e3d..73f1aad 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -91,10 +91,10 @@
                 if (DEBUG_IME_VISIBILITY) {
                     EventLog.writeEvent(IMF_SHOW_IME,
                             statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE,
-                            Objects.toString(mService.mCurFocusedWindow),
+                            Objects.toString(mService.mImeBindingState.mFocusedWindow),
                             InputMethodDebug.softInputDisplayReasonToString(reason),
                             InputMethodDebug.softInputModeToString(
-                                    mService.mCurFocusedWindowSoftInputMode));
+                                    mService.mImeBindingState.mFocusedWindowSoftInputMode));
                 }
                 mService.onShowHideSoftInputRequested(true /* show */, showInputToken, reason,
                         statsToken);
@@ -122,10 +122,10 @@
                 if (DEBUG_IME_VISIBILITY) {
                     EventLog.writeEvent(IMF_HIDE_IME,
                             statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE,
-                            Objects.toString(mService.mCurFocusedWindow),
+                            Objects.toString(mService.mImeBindingState.mFocusedWindow),
                             InputMethodDebug.softInputDisplayReasonToString(reason),
                             InputMethodDebug.softInputModeToString(
-                                    mService.mCurFocusedWindowSoftInputMode));
+                                    mService.mImeBindingState.mFocusedWindowSoftInputMode));
                 }
                 mService.onShowHideSoftInputRequested(false /* show */, hideInputToken, reason,
                         statsToken);
@@ -207,7 +207,8 @@
     @Override
     public boolean removeImeScreenshot(int displayId) {
         if (mImeTargetVisibilityPolicy.removeImeScreenshot(displayId)) {
-            mService.onShowHideSoftInputRequested(false /* show */, mService.mCurFocusedWindow,
+            mService.onShowHideSoftInputRequested(false /* show */,
+                    mService.mImeBindingState.mFocusedWindow,
                     REMOVE_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */);
             return true;
         }
diff --git a/services/core/java/com/android/server/inputmethod/ImeBindingState.java b/services/core/java/com/android/server/inputmethod/ImeBindingState.java
new file mode 100644
index 0000000..4c20c3b
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ImeBindingState.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_NAME;
+import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
+
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.server.wm.WindowManagerInternal;
+
+/**
+ * Stores information related to one active IME client on one display.
+ */
+final class ImeBindingState {
+
+    /**
+     * The last window token that we confirmed to be focused.  This is always updated upon
+     * reports from the input method client. If the window state is already changed before the
+     * report is handled, this field just keeps the last value.
+     */
+    @Nullable
+    final IBinder mFocusedWindow;
+
+    /**
+     * {@link WindowManager.LayoutParams#softInputMode} of {@link #mFocusedWindow}.
+     *
+     * @see #mFocusedWindow
+     */
+    @SoftInputModeFlags
+    final int mFocusedWindowSoftInputMode;
+
+    /**
+     * The client by which {@link #mFocusedWindow} was reported. This gets updated whenever
+     * an
+     * IME-focusable window gained focus (without necessarily starting an input connection),
+     * while {@link InputMethodManagerService#mClient} only gets updated when we actually start an
+     * input connection.
+     *
+     * @see #mFocusedWindow
+     */
+    @Nullable
+    final ClientState mFocusedWindowClient;
+
+    /**
+     * The editor info by which {@link #mFocusedWindow} was reported. This differs from
+     * {@link InputMethodManagerService#mCurEditorInfo} the same way {@link #mFocusedWindowClient}
+     * differs from {@link InputMethodManagerService#mCurClient}.
+     *
+     * @see #mFocusedWindow
+     */
+    @Nullable
+    final EditorInfo mFocusedWindowEditorInfo;
+
+    void dumpDebug(ProtoOutputStream proto, WindowManagerInternal windowManagerInternal) {
+        proto.write(CUR_FOCUSED_WINDOW_NAME,
+                windowManagerInternal.getWindowName(mFocusedWindow));
+        proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE,
+                InputMethodDebug.softInputModeToString(mFocusedWindowSoftInputMode));
+    }
+
+    void dump(String prefix, Printer p) {
+        p.println(prefix + "mFocusedWindow()=" + mFocusedWindow);
+        p.println(prefix + "softInputMode=" + InputMethodDebug.softInputModeToString(
+                mFocusedWindowSoftInputMode));
+        p.println(prefix + "mFocusedWindowClient=" + mFocusedWindowClient);
+    }
+
+    static ImeBindingState newEmptyState() {
+        return new ImeBindingState(
+                /*focusedWindow=*/ null,
+                /*focusedWindowSoftInputMode=*/ SOFT_INPUT_STATE_UNSPECIFIED,
+                /*focusedWindowClient=*/ null,
+                /*focusedWindowEditorInfo=*/ null
+        );
+    }
+
+    ImeBindingState(@Nullable IBinder focusedWindow,
+            @SoftInputModeFlags int focusedWindowSoftInputMode,
+            @Nullable ClientState focusedWindowClient,
+            @Nullable EditorInfo focusedWindowEditorInfo) {
+        mFocusedWindow = focusedWindow;
+        mFocusedWindowSoftInputMode = focusedWindowSoftInputMode;
+        mFocusedWindowClient = focusedWindowClient;
+        mFocusedWindowEditorInfo = focusedWindowEditorInfo;
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 743b8e3..cdfde87 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -548,7 +548,7 @@
             }
         }
         // Fallback to the focused window for some edge cases (e.g. relaunching the activity)
-        return mService.mCurFocusedWindow;
+        return mService.mImeBindingState.mFocusedWindow;
     }
 
     IBinder getWindowTokenFrom(ImeTargetWindowState windowState) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1f3c2f3..2205986 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -28,7 +28,6 @@
 import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD;
 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ATTRIBUTE;
 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_CLIENT;
-import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_NAME;
 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE;
 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ID;
 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_METHOD_ID;
@@ -40,7 +39,6 @@
 import static android.server.inputmethod.InputMethodManagerServiceProto.IN_FULLSCREEN_MODE;
 import static android.server.inputmethod.InputMethodManagerServiceProto.IS_INTERACTIVE;
 import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_IME_TARGET_WINDOW_NAME;
-import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_SWITCH_USER_ID;
 import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_IME_WITH_HARD_KEYBOARD;
 import static android.server.inputmethod.InputMethodManagerServiceProto.SYSTEM_READY;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -277,9 +275,6 @@
     @NonNull
     private final String[] mNonPreemptibleInputMethods;
 
-    @UserIdInt
-    private int mLastSwitchUserId;
-
     final Context mContext;
     final Resources mRes;
     private final Handler mHandler;
@@ -423,6 +418,11 @@
     private final ClientController mClientController;
 
     /**
+     * Holds the current IME binding state info.
+     */
+    ImeBindingState mImeBindingState;
+
+    /**
      * Set once the system is ready to run third party code.
      */
     boolean mSystemReady;
@@ -482,13 +482,6 @@
     private ClientState mCurClient;
 
     /**
-     * The last window token that we confirmed to be focused.  This is always updated upon reports
-     * from the input method client.  If the window state is already changed before the report is
-     * handled, this field just keeps the last value.
-     */
-    IBinder mCurFocusedWindow;
-
-    /**
      * The last window token that we confirmed that IME started talking to.  This is always updated
      * upon reports from the input method.  If the window state is already changed before the report
      * is handled, this field just keeps the last value.
@@ -496,34 +489,6 @@
     IBinder mLastImeTargetWindow;
 
     /**
-     * {@link LayoutParams#softInputMode} of {@link #mCurFocusedWindow}.
-     *
-     * @see #mCurFocusedWindow
-     */
-    @SoftInputModeFlags
-    int mCurFocusedWindowSoftInputMode;
-
-    /**
-     * The client by which {@link #mCurFocusedWindow} was reported. This gets updated whenever an
-     * IME-focusable window gained focus (without necessarily starting an input connection),
-     * while {@link #mCurClient} only gets updated when we actually start an input connection.
-     *
-     * @see #mCurFocusedWindow
-     */
-    @Nullable
-    ClientState mCurFocusedWindowClient;
-
-    /**
-     * The editor info by which {@link #mCurFocusedWindow} was reported. This differs from
-     * {@link #mCurEditorInfo} the same way {@link #mCurFocusedWindowClient} differs
-     * from {@link #mCurClient}.
-     *
-     * @see #mCurFocusedWindow
-     */
-    @Nullable
-    EditorInfo mCurFocusedWindowEditorInfo;
-
-    /**
      * The {@link IRemoteInputConnection} last provided by the current client.
      */
     IRemoteInputConnection mCurInputConnection;
@@ -1131,10 +1096,11 @@
                     mVisibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard(
                             accessibilitySoftKeyboardSetting);
                     if (mVisibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) {
-                        hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+                        hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                                 SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE);
                     } else if (isShowRequestedForCurrentWindow()) {
-                        showCurrentInputLocked(mCurFocusedWindow, InputMethodManager.SHOW_IMPLICIT,
+                        showCurrentInputLocked(mImeBindingState.mFocusedWindow,
+                                InputMethodManager.SHOW_IMPLICIT,
                                 SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE);
                     }
                 } else if (stylusHandwritingEnabledUri.equals(uri)) {
@@ -1644,7 +1610,7 @@
         }
         // Hide soft input before user switch task since switch task may block main handler a while
         // and delayed the hideCurrentInputLocked().
-        hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+        hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                 SoftInputShowHideReason.HIDE_SWITCH_USER);
         final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId,
                 clientToBeReset);
@@ -1694,8 +1660,6 @@
 
         final int userId = mActivityManagerInternal.getCurrentUserId();
 
-        mLastSwitchUserId = userId;
-
         // mSettings should be created before buildInputMethodListLocked
         mSettings = InputMethodSettings.createEmptyMap(userId);
 
@@ -1719,6 +1683,7 @@
         mClientController = new ClientController(mPackageManagerInternal);
         synchronized (ImfLock.class) {
             mClientController.addClientControllerCallback(c -> onClientRemoved(c));
+            mImeBindingState = ImeBindingState.newEmptyState();
         }
 
         mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
@@ -1881,7 +1846,6 @@
                     + " selectedIme=" + mSettings.getSelectedInputMethod());
         }
 
-        mLastSwitchUserId = newUserId;
         if (mIsInteractive && clientToBeReset != null) {
             final ClientState cs = mClientController.getClient(clientToBeReset.asBinder());
             if (cs == null) {
@@ -2233,7 +2197,7 @@
         clearClientSessionLocked(client);
         clearClientSessionForAccessibilityLocked(client);
         if (mCurClient == client) {
-            hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+            hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                     SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
             if (mBoundToMethod) {
                 mBoundToMethod = false;
@@ -2247,9 +2211,8 @@
             }
             mBoundToAccessibility = false;
             mCurClient = null;
-            if (mCurFocusedWindowClient == client) {
-                mCurFocusedWindowClient = null;
-                mCurFocusedWindowEditorInfo = null;
+            if (mImeBindingState.mFocusedWindowClient == client) {
+                mImeBindingState = ImeBindingState.newEmptyState();
             }
         }
     }
@@ -2298,7 +2261,7 @@
     @GuardedBy("ImfLock.class")
     void onUnbindCurrentMethodByReset() {
         final ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull(
-                mCurFocusedWindow);
+                mImeBindingState.mFocusedWindow);
         if (winState != null && !winState.isRequestedImeVisible()
                 && !mVisibilityStateComputer.isInputShown()) {
             // Normally, the focus window will apply the IME visibility state to
@@ -2310,7 +2273,8 @@
             // binding states in the first place.
             final var statsToken = createStatsTokenForFocusedClient(false /* show */,
                     SoftInputShowHideReason.UNBIND_CURRENT_METHOD);
-            mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, statsToken, STATE_HIDE_IME);
+            mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, statsToken,
+                    STATE_HIDE_IME);
         }
     }
 
@@ -2343,7 +2307,7 @@
     @GuardedBy("ImfLock.class")
     private boolean isShowRequestedForCurrentWindow() {
         final ImeTargetWindowState state = mVisibilityStateComputer.getWindowStateOrNull(
-                mCurFocusedWindow);
+                mImeBindingState.mFocusedWindow);
         return state != null && state.isRequestedImeVisible();
     }
 
@@ -2361,10 +2325,9 @@
                 getCurTokenLocked(),
                 mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
                 UserHandle.getUserId(mCurClient.mUid),
-                mCurClient.mSelfReportedDisplayId,
-                mCurFocusedWindow, mCurEditorInfo, mCurFocusedWindowSoftInputMode,
-                getSequenceNumberLocked());
-        mImeTargetWindowMap.put(startInputToken, mCurFocusedWindow);
+                mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo,
+                mImeBindingState.mFocusedWindowSoftInputMode, getSequenceNumberLocked());
+        mImeTargetWindowMap.put(startInputToken, mImeBindingState.mFocusedWindow);
         mStartInputHistory.addEntry(info);
 
         // Seems that PackageManagerInternal#grantImplicitAccess() doesn't handle cross-user
@@ -2391,7 +2354,7 @@
                     : createStatsTokenForFocusedClient(true /* show */,
                             SoftInputShowHideReason.ATTACH_NEW_INPUT);
             mCurStatsToken = null;
-            showCurrentInputLocked(mCurFocusedWindow, statsToken,
+            showCurrentInputLocked(mImeBindingState.mFocusedWindow, statsToken,
                     mVisibilityStateComputer.getShowFlags(), MotionEvent.TOOL_TYPE_UNKNOWN,
                     null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
         }
@@ -2465,7 +2428,7 @@
         // Compute the final shown display ID with validated cs.selfReportedDisplayId for this
         // session & other conditions.
         ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull(
-                mCurFocusedWindow);
+                mImeBindingState.mFocusedWindow);
         if (winState == null) {
             return InputBindResult.NOT_IME_TARGET_WINDOW;
         }
@@ -2484,7 +2447,7 @@
         }
 
         if (mVisibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) {
-            hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+            hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                     SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE);
             return InputBindResult.NO_IME;
         }
@@ -3653,7 +3616,8 @@
         Binder.withCleanCallingIdentity(() -> {
             Objects.requireNonNull(windowToken, "windowToken must not be null");
             synchronized (ImfLock.class) {
-                if (mCurFocusedWindow != windowToken || mCurPerceptible == perceptible) {
+                if (mImeBindingState.mFocusedWindow != windowToken
+                        || mCurPerceptible == perceptible) {
                     return;
                 }
                 mCurPerceptible = perceptible;
@@ -3747,7 +3711,7 @@
         super.hideSoftInputFromServerForTest_enforcePermission();
 
         synchronized (ImfLock.class) {
-            hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+            hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                     SoftInputShowHideReason.HIDE_SOFT_INPUT);
         }
     }
@@ -3922,7 +3886,8 @@
                     final boolean shouldClearFlag =
                             mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
                     final boolean showForced = mVisibilityStateComputer.mShowForced;
-                    if (mCurFocusedWindow != windowToken && showForced && shouldClearFlag) {
+                    if (mImeBindingState.mFocusedWindow != windowToken
+                            && showForced && shouldClearFlag) {
                         mVisibilityStateComputer.mShowForced = false;
                     }
 
@@ -3940,7 +3905,7 @@
                         Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
                                 + " a background user, use EditorInfo.targetInputMethodUser with"
                                 + " INTERACT_ACROSS_USERS_FULL permission.");
-                        hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+                        hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                                 SoftInputShowHideReason.HIDE_INVALID_USER);
                         return InputBindResult.INVALID_USER;
                     }
@@ -4001,7 +3966,7 @@
                     + " cs=" + cs);
         }
 
-        final boolean sameWindowFocused = mCurFocusedWindow == windowToken;
+        final boolean sameWindowFocused = mImeBindingState.mFocusedWindow == windowToken;
         final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
         final boolean startInputByWinGainedFocus =
                 (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0;
@@ -4032,10 +3997,7 @@
                     null, null, null, null, -1, false);
         }
 
-        mCurFocusedWindow = windowToken;
-        mCurFocusedWindowSoftInputMode = softInputMode;
-        mCurFocusedWindowClient = cs;
-        mCurFocusedWindowEditorInfo = editorInfo;
+        mImeBindingState = new ImeBindingState(windowToken, softInputMode, cs, editorInfo);
         mCurPerceptible = true;
 
         // We want to start input before showing the IME, but after closing
@@ -4066,9 +4028,8 @@
                     break;
             }
             final var statsToken = createStatsTokenForFocusedClient(isShow, imeVisRes.getReason());
-            mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, statsToken,
+            mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, statsToken,
                     imeVisRes.getState(), imeVisRes.getReason());
-
             if (imeVisRes.getReason() == SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW) {
                 // If focused display changed, we should unbind current method
                 // to make app window in previous display relayout after Ime
@@ -4107,7 +4068,7 @@
                 throw new IllegalArgumentException("unknown client " + client.asBinder());
             }
             ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
-            if (!isImeClientFocused(mCurFocusedWindow, cs)) {
+            if (!isImeClientFocused(mImeBindingState.mFocusedWindow, cs)) {
                 Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client));
                 return false;
             }
@@ -4119,8 +4080,8 @@
     @GuardedBy("ImfLock.class")
     private boolean canShowInputMethodPickerLocked(IInputMethodClient client) {
         final int uid = Binder.getCallingUid();
-        if (mCurFocusedWindowClient != null && client != null
-                && mCurFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
+        if (mImeBindingState.mFocusedWindowClient != null && client != null
+                && mImeBindingState.mFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
             return true;
         }
         if (mSettings.getUserId() != UserHandle.getUserId(uid)) {
@@ -4743,12 +4704,11 @@
             proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked());
             proto.write(CUR_SEQ, getSequenceNumberLocked());
             proto.write(CUR_CLIENT, Objects.toString(mCurClient));
-            proto.write(CUR_FOCUSED_WINDOW_NAME,
-                    mWindowManagerInternal.getWindowName(mCurFocusedWindow));
+            mImeBindingState.dumpDebug(proto, mWindowManagerInternal);
             proto.write(LAST_IME_TARGET_WINDOW_NAME,
                     mWindowManagerInternal.getWindowName(mLastImeTargetWindow));
-            proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE,
-                    InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
+            proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString(
+                    mImeBindingState.mFocusedWindowSoftInputMode));
             if (mCurEditorInfo != null) {
                 mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE);
             }
@@ -4758,7 +4718,6 @@
             proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked()));
             proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId);
             proto.write(SYSTEM_READY, mSystemReady);
-            proto.write(LAST_SWITCH_USER_ID, mLastSwitchUserId);
             proto.write(HAVE_CONNECTION, hasConnectionLocked());
             proto.write(BOUND_TO_METHOD, mBoundToMethod);
             proto.write(IS_INTERACTIVE, mIsInteractive);
@@ -4870,12 +4829,12 @@
         final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken);
         final WindowManagerInternal.ImeTargetInfo info =
                 mWindowManagerInternal.onToggleImeRequested(
-                        show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
+                        show, mImeBindingState.mFocusedWindow, requestToken, mCurTokenDisplayId);
         mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
-                mCurFocusedWindowClient, mCurFocusedWindowEditorInfo, info.focusedWindowName,
-                mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
-                info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
-                info.imeSurfaceParentName));
+                mImeBindingState.mFocusedWindowClient, mImeBindingState.mFocusedWindowEditorInfo,
+                info.focusedWindowName, mImeBindingState.mFocusedWindowSoftInputMode, reason,
+                mInFullscreenMode, info.requestWindowName, info.imeControlTargetName,
+                info.imeLayerTargetName, info.imeSurfaceParentName));
 
         if (statsToken != null) {
             mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName);
@@ -5046,8 +5005,7 @@
             case MSG_HIDE_ALL_INPUT_METHODS:
                 synchronized (ImfLock.class) {
                     @SoftInputShowHideReason final int reason = (int) msg.obj;
-                    hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, reason);
-
+                    hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, reason);
                 }
                 return true;
             case MSG_REMOVE_IME_SURFACE: {
@@ -5066,7 +5024,7 @@
                 IBinder windowToken = (IBinder) msg.obj;
                 synchronized (ImfLock.class) {
                     try {
-                        if (windowToken == mCurFocusedWindow
+                        if (windowToken == mImeBindingState.mFocusedWindow
                                 && mEnabledSession != null && mEnabledSession.mSession != null) {
                             mEnabledSession.mSession.removeImeSurface();
                         }
@@ -5141,7 +5099,7 @@
             case MSG_START_HANDWRITING:
                 synchronized (ImfLock.class) {
                     IInputMethodInvoker curMethod = getCurMethodLocked();
-                    if (curMethod == null || mCurFocusedWindow == null) {
+                    if (curMethod == null || mImeBindingState.mFocusedWindow == null) {
                         return true;
                     }
                     final HandwritingModeController.HandwritingSession session =
@@ -5149,7 +5107,7 @@
                                     msg.arg1 /*requestId*/,
                                     msg.arg2 /*pid*/,
                                     mBindingController.getCurMethodUid(),
-                                    mCurFocusedWindow);
+                                    mImeBindingState.mFocusedWindow);
                     if (session == null) {
                         Slog.e(TAG,
                                 "Failed to start handwriting session for requestId: " + msg.arg1);
@@ -5202,11 +5160,11 @@
                 // Handle IME visibility when interactive changed before finishing the input to
                 // ensure we preserve the last state as possible.
                 final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.onInteractiveChanged(
-                        mCurFocusedWindow, interactive);
+                        mImeBindingState.mFocusedWindow, interactive);
                 if (imeVisRes != null) {
                     // Pass in a null statsToken as the IME snapshot is not tracked by ImeTracker.
-                    mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, null /* statsToken */,
-                            imeVisRes.getState(), imeVisRes.getReason());
+                    mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow,
+                            null /* statsToken */, imeVisRes.getState(), imeVisRes.getReason());
                 }
                 // Eligible IME processes use new "setInteractive" protocol.
                 mCurClient.mClient.setInteractive(mIsInteractive, mInFullscreenMode);
@@ -5896,7 +5854,7 @@
         @Override
         public void reportImeControl(@Nullable IBinder windowToken) {
             synchronized (ImfLock.class) {
-                if (mCurFocusedWindow != windowToken) {
+                if (mImeBindingState.mFocusedWindow != windowToken) {
                     // mCurPerceptible was set by the focused window, but it is no longer in
                     // control, so we reset mCurPerceptible.
                     mCurPerceptible = true;
@@ -5910,7 +5868,7 @@
                 // Hide the IME method menu only when the IME surface parent is changed by the
                 // input target changed, in case seeing the dialog dismiss flickering during
                 // the next focused window starting the input connection.
-                if (mLastImeTargetWindow != mCurFocusedWindow) {
+                if (mLastImeTargetWindow != mImeBindingState.mFocusedWindow) {
                     mMenuController.hideInputMethodMenuLocked();
                 }
             }
@@ -6204,11 +6162,7 @@
             client = mCurClient;
             p.println("  mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
             p.println("  mCurPerceptible=" + mCurPerceptible);
-            p.println("  mCurFocusedWindow=" + mCurFocusedWindow
-                    + " softInputMode="
-                    + InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode)
-                    + " client=" + mCurFocusedWindowClient);
-            focusedWindowClient = mCurFocusedWindowClient;
+            mImeBindingState.dump("  ", p);
             p.println("  mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked()
                     + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
                     + mBindingController.isVisibleBound());
@@ -6259,7 +6213,8 @@
             p.println("No input method client.");
         }
 
-        if (focusedWindowClient != null && client != focusedWindowClient) {
+        if (mImeBindingState.mFocusedWindowClient != null
+                && client != mImeBindingState.mFocusedWindowClient) {
             p.println(" ");
             p.println("Warning: Current input method client doesn't match the last focused. "
                     + "window.");
@@ -6267,7 +6222,8 @@
             p.println(" ");
             pw.flush();
             try {
-                TransferPipe.dumpAsync(focusedWindowClient.mClient.asBinder(), fd, args);
+                TransferPipe.dumpAsync(
+                        mImeBindingState.mFocusedWindowClient.mClient.asBinder(), fd, args);
             } catch (IOException | RemoteException e) {
                 p.println("Failed to dump input method client in focused window: " + e);
             }
@@ -6339,8 +6295,6 @@
         @ShellCommandResult
         private int onCommandWithSystemIdentity(@Nullable String cmd) {
             switch (TextUtils.emptyIfNull(cmd)) {
-                case "get-last-switch-user-id":
-                    return mService.getLastSwitchUserId(this);
                 case "tracing":
                     return mService.handleShellCommandTraceInputMethod(this);
                 case "ime": {  // For "adb shell ime <command>".
@@ -6454,15 +6408,6 @@
     // ----------------------------------------------------------------------
     // Shell command handlers:
 
-    @BinderThread
-    @ShellCommandResult
-    private int getLastSwitchUserId(@NonNull ShellCommand shellCommand) {
-        synchronized (ImfLock.class) {
-            shellCommand.getOutPrintWriter().println(mLastSwitchUserId);
-            return ShellCommandResult.SUCCESS;
-        }
-    }
-
     /**
      * Handles {@code adb shell ime list}.
      * @param shellCommand {@link ShellCommand} object that is handling this command.
@@ -6713,7 +6658,7 @@
                     final String nextIme;
                     final List<InputMethodInfo> nextEnabledImes;
                     if (userId == mSettings.getUserId()) {
-                        hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+                        hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
                                 SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
                         mBindingController.unbindCurrentMethod();
 
@@ -6847,11 +6792,11 @@
     @NonNull
     private ImeTracker.Token createStatsTokenForFocusedClient(boolean show,
             @SoftInputShowHideReason int reason) {
-        final int uid = mCurFocusedWindowClient != null
-                ? mCurFocusedWindowClient.mUid
+        final int uid = mImeBindingState.mFocusedWindowClient != null
+                ? mImeBindingState.mFocusedWindowClient.mUid
                 : -1;
-        final var packageName = mCurFocusedWindowEditorInfo != null
-                ? mCurFocusedWindowEditorInfo.packageName
+        final var packageName = mImeBindingState.mFocusedWindowEditorInfo != null
+                ? mImeBindingState.mFocusedWindowEditorInfo.packageName
                 : "uid(" + uid + ")";
 
         return ImeTracker.forLogging().onStart(packageName, uid,
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3a7ac0b..ba5882c 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -12050,11 +12050,12 @@
         @Override
         public void onServiceAdded(ManagedServiceInfo info) {
             if (lifetimeExtensionRefactor()) {
-                // Generally, only System or System UI should have the permissions to call
-                // registerSystemService.
-                // isCallerSystemorPhone tells us whether the caller is System. Then, if it's not
-                // the system, we know it's system UI.
-                info.isSystemUi = !isCallerSystemOrPhone();
+                // We explicitly check the status bar permission for the uid in the info object.
+                // We can't use the calling uid here because it's probably always system server.
+                // Note that this will also be true for the shell.
+                info.isSystemUi = getContext().checkPermission(
+                        android.Manifest.permission.STATUS_BAR_SERVICE, -1, info.uid)
+                        == PERMISSION_GRANTED;
             }
             final INotificationListener listener = (INotificationListener) info.service;
             final NotificationRankingUpdate update;
diff --git a/services/core/java/com/android/server/notification/ZenAdapters.java b/services/core/java/com/android/server/notification/ZenAdapters.java
deleted file mode 100644
index 37b263c..0000000
--- a/services/core/java/com/android/server/notification/ZenAdapters.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.notification;
-
-import android.app.Flags;
-import android.app.NotificationManager.Policy;
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenPolicy;
-
-/**
- * Converters between different Zen representations.
- */
-class ZenAdapters {
-
-    static ZenPolicy notificationPolicyToZenPolicy(Policy policy) {
-        ZenPolicy.Builder zenPolicyBuilder = new ZenPolicy.Builder()
-                .allowAlarms(policy.allowAlarms())
-                .allowCalls(
-                        policy.allowCalls()
-                                ? ZenModeConfig.getZenPolicySenders(policy.allowCallsFrom())
-                                : ZenPolicy.PEOPLE_TYPE_NONE)
-                .allowConversations(
-                        policy.allowConversations()
-                                ? notificationPolicyConversationSendersToZenPolicy(
-                                        policy.allowConversationsFrom())
-                                : ZenPolicy.CONVERSATION_SENDERS_NONE)
-                .allowEvents(policy.allowEvents())
-                .allowMedia(policy.allowMedia())
-                .allowMessages(
-                        policy.allowMessages()
-                                ? ZenModeConfig.getZenPolicySenders(policy.allowMessagesFrom())
-                                : ZenPolicy.PEOPLE_TYPE_NONE)
-                .allowReminders(policy.allowReminders())
-                .allowRepeatCallers(policy.allowRepeatCallers())
-                .allowSystem(policy.allowSystem());
-
-        if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
-            zenPolicyBuilder.showBadges(policy.showBadges())
-                    .showFullScreenIntent(policy.showFullScreenIntents())
-                    .showInAmbientDisplay(policy.showAmbient())
-                    .showInNotificationList(policy.showInNotificationList())
-                    .showLights(policy.showLights())
-                    .showPeeking(policy.showPeeking())
-                    .showStatusBarIcons(policy.showStatusBarIcons());
-        }
-
-        if (Flags.modesApi()) {
-            zenPolicyBuilder.allowPriorityChannels(policy.allowPriorityChannels());
-        }
-
-        return zenPolicyBuilder.build();
-    }
-
-    @ZenPolicy.ConversationSenders
-    private static int notificationPolicyConversationSendersToZenPolicy(
-            int npPriorityConversationSenders) {
-        switch (npPriorityConversationSenders) {
-            case Policy.CONVERSATION_SENDERS_ANYONE:
-                return ZenPolicy.CONVERSATION_SENDERS_ANYONE;
-            case Policy.CONVERSATION_SENDERS_IMPORTANT:
-                return ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
-            case Policy.CONVERSATION_SENDERS_NONE:
-                return ZenPolicy.CONVERSATION_SENDERS_NONE;
-            case Policy.CONVERSATION_SENDERS_UNSET:
-            default:
-                return ZenPolicy.CONVERSATION_SENDERS_UNSET;
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index b9a267f..8e37b4f 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -32,6 +32,7 @@
 import android.content.pm.PackageManager;
 import android.os.Process;
 import android.service.notification.DNDPolicyProto;
+import android.service.notification.ZenAdapters;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
 import android.service.notification.ZenModeConfig.ZenRule;
@@ -591,9 +592,11 @@
                 // This applies to both call and message senders, but not conversation senders,
                 // where they use the same enum values.
                 proto.write(DNDPolicyProto.ALLOW_CALLS_FROM,
-                        ZenModeConfig.getZenPolicySenders(mNewPolicy.allowCallsFrom()));
+                        ZenAdapters.notificationPolicySendersToZenPolicyPeopleType(
+                                mNewPolicy.allowCallsFrom()));
                 proto.write(DNDPolicyProto.ALLOW_MESSAGES_FROM,
-                        ZenModeConfig.getZenPolicySenders(mNewPolicy.allowMessagesFrom()));
+                        ZenAdapters.notificationPolicySendersToZenPolicyPeopleType(
+                                mNewPolicy.allowMessagesFrom()));
                 proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM,
                         mNewPolicy.allowConversationsFrom());
 
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 6857869..bc86c82 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -84,6 +84,7 @@
 import android.service.notification.Condition;
 import android.service.notification.ConditionProviderService;
 import android.service.notification.DeviceEffectsApplier;
+import android.service.notification.ZenAdapters;
 import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 8452c0e..4f86adf 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -21,7 +21,6 @@
 import android.Manifest;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.role.RoleManager;
@@ -39,6 +38,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -96,6 +96,7 @@
     private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
 
     private final Object mLock = new Object();
+    private final Injector mInjector;
     private final Context mContext;
     private final AppOpsManager mAppOps;
     private final TelephonyManager mTelephonyManager;
@@ -345,6 +346,14 @@
         AtomicFile getMappingFile() {
             return mMappingFile;
         }
+
+        UserManager getUserManager() {
+            return mContext.getSystemService(UserManager.class);
+        }
+
+        DevicePolicyManager getDevicePolicyManager() {
+            return mContext.getSystemService(DevicePolicyManager.class);
+        }
     }
 
     BugreportManagerServiceImpl(Context context) {
@@ -356,6 +365,7 @@
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     BugreportManagerServiceImpl(Injector injector) {
+        mInjector = injector;
         mContext = injector.getContext();
         mAppOps = mContext.getSystemService(AppOpsManager.class);
         mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
@@ -388,12 +398,7 @@
         int callingUid = Binder.getCallingUid();
         enforcePermission(callingPackage, callingUid, bugreportMode
                 == BugreportParams.BUGREPORT_MODE_TELEPHONY /* checkCarrierPrivileges */);
-        final long identity = Binder.clearCallingIdentity();
-        try {
-            ensureUserCanTakeBugReport(bugreportMode);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
-        }
+        ensureUserCanTakeBugReport(bugreportMode);
 
         Slogf.i(TAG, "Starting bugreport for %s / %d", callingPackage, callingUid);
         synchronized (mLock) {
@@ -432,7 +437,6 @@
     @RequiresPermission(value = Manifest.permission.DUMP, conditional = true)
     public void retrieveBugreport(int callingUidUnused, String callingPackage, int userId,
             FileDescriptor bugreportFd, String bugreportFile,
-
             boolean keepBugreportOnRetrievalUnused, IDumpstateListener listener) {
         int callingUid = Binder.getCallingUid();
         enforcePermission(callingPackage, callingUid, false);
@@ -564,54 +568,59 @@
     }
 
     /**
-     * Validates that the current user is an admin user or, when bugreport is requested remotely
-     * that the current user is an affiliated user.
+     * Validates that the calling user is an admin user or, when bugreport is requested remotely
+     * that the user is an affiliated user.
      *
-     * @throws IllegalArgumentException if the current user is not an admin user
+     * @throws IllegalArgumentException if the calling user or the parent of the calling profile
+     *                                  user is not an admin user.
      */
     private void ensureUserCanTakeBugReport(int bugreportMode) {
-        UserInfo currentUser = null;
+        // Get the calling userId before clearing the caller identity.
+        int effectiveCallingUserId = UserHandle.getUserId(Binder.getCallingUid());
+        boolean isAdminUser = false;
+        final long identity = Binder.clearCallingIdentity();
         try {
-            currentUser = ActivityManager.getService().getCurrentUser();
-        } catch (RemoteException e) {
-            // Impossible to get RemoteException for an in-process call.
+            UserInfo profileParent =
+                    mInjector.getUserManager().getProfileParent(effectiveCallingUserId);
+            if (profileParent == null) {
+                isAdminUser = mInjector.getUserManager().isUserAdmin(effectiveCallingUserId);
+            } else {
+                // If the caller is a profile, we need to check its parent user instead.
+                // Therefore setting the profile parent user as the effective calling user.
+                effectiveCallingUserId = profileParent.id;
+                isAdminUser = profileParent.isAdmin();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
-
-        if (currentUser == null) {
-            logAndThrow("There is no current user, so no bugreport can be requested.");
-        }
-
-        if (!currentUser.isAdmin()) {
+        if (!isAdminUser) {
             if (bugreportMode == BugreportParams.BUGREPORT_MODE_REMOTE
-                    && isCurrentUserAffiliated(currentUser.id)) {
+                    && isUserAffiliated(effectiveCallingUserId)) {
                 return;
             }
-            logAndThrow(TextUtils.formatSimple("Current user %s is not an admin user."
-                    + " Only admin users are allowed to take bugreport.", currentUser.id));
+            logAndThrow(TextUtils.formatSimple("Calling user %s is not an admin user."
+                    + " Only admin users and their profiles are allowed to take bugreport.",
+                    effectiveCallingUserId));
         }
     }
 
     /**
-     * Returns {@code true} if the device has device owner and the current user is affiliated
+     * Returns {@code true} if the device has device owner and the specified user is affiliated
      * with the device owner.
      */
-    private boolean isCurrentUserAffiliated(int currentUserId) {
-        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+    private boolean isUserAffiliated(int userId) {
+        DevicePolicyManager dpm = mInjector.getDevicePolicyManager();
         int deviceOwnerUid = dpm.getDeviceOwnerUserId();
         if (deviceOwnerUid == UserHandle.USER_NULL) {
             return false;
         }
 
-        int callingUserId = UserHandle.getUserId(Binder.getCallingUid());
-
-        Slog.i(TAG, "callingUid: " + callingUserId + " deviceOwnerUid: " + deviceOwnerUid
-                + " currentUserId: " + currentUserId);
-
-        if (callingUserId != deviceOwnerUid) {
-            logAndThrow("Caller is not device owner on provisioned device.");
+        if (DEBUG) {
+            Slog.d(TAG, "callingUid: " + userId + " deviceOwnerUid: " + deviceOwnerUid);
         }
-        if (!dpm.isAffiliatedUser(currentUserId)) {
-            logAndThrow("Current user is not affiliated to the device owner.");
+
+        if (userId != deviceOwnerUid && !dpm.isAffiliatedUser(userId)) {
+            logAndThrow("User " + userId + " is not affiliated to the device owner.");
         }
         return true;
     }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 186cf5e..4bfd077 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1080,7 +1080,7 @@
                     reconciledPackages = ReconcilePackageUtils.reconcilePackages(
                             requests, Collections.unmodifiableMap(mPm.mPackages),
                             versionInfos, mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
-                            mPm.mSettings);
+                            mPm.mSettings, mPm.mInjector.getSystemConfig());
                 } catch (ReconcileFailure e) {
                     for (InstallRequest request : requests) {
                         request.setError("Reconciliation failed...", e);
@@ -3810,7 +3810,7 @@
                                 mPm.mPackages, Collections.singletonMap(pkgName,
                                         mPm.getSettingsVersionForPackage(parsedPackage)),
                                 mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
-                                mPm.mSettings);
+                                mPm.mSettings, mPm.mInjector.getSystemConfig());
                 if ((scanFlags & SCAN_AS_APEX) == 0) {
                     appIdCreated = optimisticallyRegisterAppId(installRequest);
                 } else {
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index 9a7916a..90d6adc 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -17,6 +17,7 @@
 package com.android.server.pm;
 
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
 import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY;
 
@@ -25,6 +26,7 @@
 import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
 import static com.android.server.pm.PackageManagerService.TAG;
 
+import android.content.pm.Flags;
 import android.content.pm.PackageManager;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SigningDetails;
@@ -36,6 +38,7 @@
 
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.SystemConfig;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.utils.WatchedLongSparseArray;
 
@@ -53,14 +56,17 @@
  * as install) led to the request.
  */
 final class ReconcilePackageUtils {
-    private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE = Build.IS_DEBUGGABLE || true;
+    // TODO(b/308573259): with allow-list, we should be able to disallow such installs even in
+    // debuggable builds.
+    private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SHAREDUIDS = Build.IS_DEBUGGABLE
+            || !Flags.restrictNonpreloadsSystemShareduids();
 
     public static List<ReconciledPackage> reconcilePackages(
             List<InstallRequest> installRequests,
             Map<String, AndroidPackage> allPackages,
             Map<String, Settings.VersionInfo> versionInfos,
             SharedLibrariesImpl sharedLibraries,
-            KeySetManagerService ksms, Settings settings)
+            KeySetManagerService ksms, Settings settings, SystemConfig systemConfig)
             throws ReconcileFailure {
         final List<ReconciledPackage> result = new ArrayList<>(installRequests.size());
 
@@ -187,11 +193,19 @@
                                     SigningDetails.CertCapabilities.PERMISSION)) {
                         Slog.d(TAG, "Non-preload app associated with system signature: "
                                 + signatureCheckPs.getPackageName());
-                        if (!ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE) {
-                            throw new ReconcileFailure(
-                                    INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
-                                    "Non-preload app associated with system signature: "
-                                            + signatureCheckPs.getPackageName());
+                        if (sharedUserSetting != null && !ALLOW_NON_PRELOADS_SYSTEM_SHAREDUIDS) {
+                            // Check the allow-list.
+                            var allowList = systemConfig.getPackageToSharedUidAllowList();
+                            var sharedUidName = allowList.get(signatureCheckPs.getPackageName());
+                            if (sharedUidName == null
+                                    || !sharedUserSetting.name.equals(sharedUidName)) {
+                                var msg = "Non-preload app " + signatureCheckPs.getPackageName()
+                                        + " signed with platform signature and joining shared uid: "
+                                        + sharedUserSetting.name;
+                                Slog.e(TAG, msg + ", allowList: " + allowList);
+                                throw new ReconcileFailure(
+                                        INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID, msg);
+                            }
                         }
                     }
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index fe65010..aaf21c8 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -4558,6 +4558,10 @@
             for (int i = 0; i < size; i++) {
                 final PackageSetting ps = mPackages.valueAt(i);
                 if (ps.getPkg() == null) {
+                    // This would force-create correct per-user state.
+                    ps.setInstalled(false, userHandle);
+                    // Make sure the app is excluded from storage mapping for this user.
+                    writeKernelMappingLPr(ps);
                     continue;
                 }
                 final boolean shouldMaybeInstall = ps.isSystem() &&
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index c1ab3f9..211b754 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -3795,6 +3795,7 @@
                 }
 
                 final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                final boolean archival = intent.getBooleanExtra(Intent.EXTRA_ARCHIVAL, false);
 
                 switch (action) {
                     case Intent.ACTION_PACKAGE_ADDED:
@@ -3805,7 +3806,7 @@
                         }
                         break;
                     case Intent.ACTION_PACKAGE_REMOVED:
-                        if (!replacing) {
+                        if (!replacing || (replacing && archival)) {
                             handlePackageRemoved(packageName, userId);
                         }
                         break;
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index 47032ea..754b141 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -408,6 +408,9 @@
 
     /**
      * Gets the permission states for requested package, persistent device and user.
+     * <p>
+     * <strong>Note: </strong>Default device permissions are not inherited in this API. Returns the
+     * exact permission states for the requested device.
      *
      * @param packageName name of the package you are checking against
      * @param deviceId id of the persistent device you are checking against
diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
index 32a21c5..cebf7fb 100644
--- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
+++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
@@ -21,7 +21,6 @@
 import static android.Manifest.permission.WRITE_MEDIA_STORAGE;
 import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
 import static android.app.AppOpsManager.OP_NONE;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
@@ -148,7 +147,7 @@
                             pkg.hasPreserveLegacyExternalStorage();
                     targetSDK = getMinimumTargetSDK(context, appInfo, user);
 
-                    shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
+                    shouldApplyRestriction = !isWhiteListed;
                     isForcedScopedStorage = sForcedScopedStorageAppWhitelist
                             .contains(appInfo.packageName);
                 } else {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 601c7f4..9616c28 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -39,6 +39,7 @@
 import android.view.DisplayInfo;
 import android.view.View;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.utils.TimingsTraceAndSlog;
 
 import libcore.io.IoUtils;
@@ -65,7 +66,7 @@
      * Maximum acceptable parallax.
      * A value of 1 means "the additional width for parallax is at most 100% of the screen width"
      */
-    private static final float MAX_PARALLAX = 1f;
+    @VisibleForTesting static final float MAX_PARALLAX = 1f;
 
     /**
      * We define three ways to adjust a crop. These modes are used depending on the situation:
@@ -73,10 +74,9 @@
      *   - When going from folded to unfolded, we want to add content
      *   - For a screen rotation, we want to keep the same amount of content
      */
-    private static final int ADD = 1;
-    private static final int REMOVE = 2;
-    private static final int BALANCE = 3;
-
+    @VisibleForTesting static final int ADD = 1;
+    @VisibleForTesting static final int REMOVE = 2;
+    @VisibleForTesting static final int BALANCE = 3;
 
     private final WallpaperDisplayHelper mWallpaperDisplayHelper;
 
@@ -209,7 +209,8 @@
      * Given a crop, a displaySize for the orientation of that crop, compute the visible part of the
      * crop. This removes any additional width used for parallax. No-op if displaySize == null.
      */
-    private static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) {
+    @VisibleForTesting
+    static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) {
         if (displaySize == null) return crop;
         Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
         // only keep the visible part (without parallax)
@@ -240,12 +241,13 @@
      *     </li>
      * </ul>
      */
-    private static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize,
+    @VisibleForTesting
+    static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize,
             boolean parallax, boolean rtl, int mode) {
         Rect adjustedCrop = new Rect(crop);
         float cropRatio = ((float) crop.width()) / crop.height();
         float screenRatio = ((float) screenSize.x) / screenSize.y;
-        if (cropRatio >= screenRatio) {
+        if (cropRatio > screenRatio) {
             if (!parallax) {
                 // rotate everything 90 degrees clockwise, compute the result, and rotate back
                 int newLeft = bitmapSize.y - crop.bottom;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7d5aa96d..d30a216 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -553,7 +553,6 @@
     boolean launchFailed;   // set if a launched failed, to abort on 2nd try
     boolean delayedResume;  // not yet resumed because of stopped app switches?
     boolean finishing;      // activity in pending finish list?
-    int configChangeFlags;  // which config values have changed
     private boolean keysPaused;     // has key dispatching been paused for it?
     int launchMode;         // the launch mode activity attribute.
     int lockTaskLaunchMode; // the lockTaskMode manifest attribute, subject to override
@@ -1295,10 +1294,6 @@
         if (mDeferHidingClient) {
             pw.println(prefix + "mDeferHidingClient=" + mDeferHidingClient);
         }
-        if (configChangeFlags != 0) {
-            pw.print(prefix); pw.print(" configChangeFlags=");
-                    pw.println(Integer.toHexString(configChangeFlags));
-        }
         if (mServiceConnectionsHolder != null) {
             pw.print(prefix); pw.print("connections="); pw.println(mServiceConnectionsHolder);
         }
@@ -4064,7 +4059,7 @@
             try {
                 if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this);
                 mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                        DestroyActivityItem.obtain(token, finishing, configChangeFlags));
+                        DestroyActivityItem.obtain(token, finishing));
             } catch (Exception e) {
                 // We can just ignore exceptions here...  if the process has crashed, our death
                 // notification will clean things up.
@@ -4106,8 +4101,6 @@
             }
         }
 
-        configChangeFlags = 0;
-
         return removedFromHistory;
     }
 
@@ -5026,7 +5019,7 @@
                 return PauseActivityItem.obtain(token);
             case STOPPING:
             case STOPPED:
-                return StopActivityItem.obtain(token, configChangeFlags);
+                return StopActivityItem.obtain(token);
             default:
                 // Do not send a result immediately if the activity is in state INITIALIZING,
                 // RESTARTING_PROCESS, FINISHING, DESTROYING, or DESTROYED.
@@ -6300,7 +6293,7 @@
             try {
                 mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
                         PauseActivityItem.obtain(token, finishing, false /* userLeaving */,
-                                configChangeFlags, false /* dontReport */, mAutoEnteringPip));
+                                false /* dontReport */, mAutoEnteringPip));
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
             }
@@ -6614,7 +6607,7 @@
             EventLogTags.writeWmStopActivity(
                     mUserId, System.identityHashCode(this), shortComponentName);
             mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                    StopActivityItem.obtain(token, configChangeFlags));
+                    StopActivityItem.obtain(token));
 
             mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT);
         } catch (Exception e) {
@@ -9858,7 +9851,6 @@
 
         if (shouldRelaunchLocked(changes, mTmpConfig)) {
             // Aha, the activity isn't handling the change, so DIE DIE DIE.
-            configChangeFlags |= changes;
             if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating
                     && !mTransitionController.isShellTransitionsEnabled()) {
                 startFreezingScreenLocked(app, mAtmService.mTmpUpdateConfigurationResult.changes);
@@ -9887,7 +9879,7 @@
                 ProtoLog.v(WM_DEBUG_STATES, "Config is relaunching invisible "
                         + "activity %s called by %s", this, Debug.getCallers(4));
             }
-            relaunchActivityLocked(preserveWindow);
+            relaunchActivityLocked(preserveWindow, changes);
 
             // All done...  tell the caller we weren't able to keep this activity around.
             return false;
@@ -10029,9 +10021,8 @@
                 | CONFIG_SCREEN_LAYOUT)) != 0;
     }
 
-    void relaunchActivityLocked(boolean preserveWindow) {
+    void relaunchActivityLocked(boolean preserveWindow, int configChangeFlags) {
         if (mAtmService.mSuppressResizeConfigChanges && preserveWindow) {
-            configChangeFlags = 0;
             return;
         }
         if (!preserveWindow) {
@@ -10104,8 +10095,6 @@
 
         // The activity may be waiting for stop, but that is no longer appropriate for it.
         mTaskSupervisor.mStoppingActivities.remove(this);
-
-        configChangeFlags = 0;
     }
 
     /**
@@ -10174,7 +10163,7 @@
         // {@link ActivityTaskManagerService.activityStopped}).
         try {
             mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                    StopActivityItem.obtain(token, 0 /* configChanges */));
+                    StopActivityItem.obtain(token));
         } catch (RemoteException e) {
             Slog.w(TAG, "Exception thrown during restart " + this, e);
         }
diff --git a/services/core/java/com/android/server/wm/SnapshotCache.java b/services/core/java/com/android/server/wm/SnapshotCache.java
index 64d8c75..8680436 100644
--- a/services/core/java/com/android/server/wm/SnapshotCache.java
+++ b/services/core/java/com/android/server/wm/SnapshotCache.java
@@ -16,7 +16,6 @@
 package com.android.server.wm;
 
 import android.annotation.Nullable;
-import android.hardware.HardwareBuffer;
 import android.util.ArrayMap;
 import android.window.TaskSnapshot;
 
@@ -93,10 +92,6 @@
             if (entry != null) {
                 mAppIdMap.remove(entry.topApp);
                 mRunningCache.remove(id);
-                final HardwareBuffer buffer = entry.snapshot.getHardwareBuffer();
-                if (buffer != null) {
-                    buffer.close();
-                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 85d81c4..78ababc 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1881,7 +1881,7 @@
 
             mAtmService.getLifecycleManager().scheduleTransactionItem(prev.app.getThread(),
                     PauseActivityItem.obtain(prev.token, prev.finishing, userLeaving,
-                            prev.configChangeFlags, pauseImmediately, autoEnteringPip));
+                            pauseImmediately, autoEnteringPip));
         } catch (Exception e) {
             // Ignore exception, if process died other code will cleanup.
             Slog.w(TAG, "Exception thrown during pause", e);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 594043d..9b19a70 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -349,7 +349,7 @@
         final Rect lastWallpaperBounds = wallpaperWin.getParentFrame();
         int screenWidth = lastWallpaperBounds.width();
         int screenHeight = lastWallpaperBounds.height();
-        float screenRatio = ((float) screenWidth) / screenHeight;
+        float screenRatio = (float) screenWidth / screenHeight;
         Point screenSize = new Point(screenWidth, screenHeight);
 
         WallpaperWindowToken token = wallpaperWin.mToken.asWallpaperToken();
@@ -399,20 +399,32 @@
             Point bitmapSize = new Point(
                     wallpaperWin.mRequestedWidth, wallpaperWin.mRequestedHeight);
             SparseArray<Rect> cropHints = token.getCropHints();
-            wallpaperFrame = mWallpaperCropUtils.getCrop(
-                    screenSize, bitmapSize, cropHints, wallpaperWin.isRtl());
+            wallpaperFrame = bitmapSize.x <= 0 || bitmapSize.y <= 0 ? wallpaperWin.getFrame()
+                    : mWallpaperCropUtils.getCrop(screenSize, bitmapSize, cropHints,
+                            wallpaperWin.isRtl());
+            int frameWidth = wallpaperFrame.width();
+            int frameHeight = wallpaperFrame.height();
+            float frameRatio = (float) frameWidth / frameHeight;
 
-            cropZoom = wallpaperFrame.isEmpty() ? 1f
-                    : ((float) screenHeight) / wallpaperFrame.height() / wallpaperWin.mVScale;
+            // If the crop is proportionally wider/taller than the screen, scale it so that its
+            // height/width matches the screen height/width, and use the additional width/height
+            // for parallax (respectively).
+            boolean scaleHeight = frameRatio >= screenRatio;
+            cropZoom = wallpaperFrame.isEmpty() ? 1f : scaleHeight
+                    ? (float) screenHeight / frameHeight / wallpaperWin.mVScale
+                    : (float) screenWidth / frameWidth / wallpaperWin.mHScale;
 
-            // A positive x / y offset shifts the wallpaper to the right / bottom respectively.
-            cropOffsetX = -wallpaperFrame.left
-                    + (int) ((cropZoom - 1f) * wallpaperFrame.height() * screenRatio / 2f);
-            cropOffsetY = -wallpaperFrame.top
-                    + (int) ((cropZoom - 1f) * wallpaperFrame.height() / 2f);
+            // The dimensions of the frame, without the additional width or height for parallax.
+            float w = scaleHeight ? frameHeight * screenRatio : frameWidth;
+            float h = scaleHeight ? frameHeight : frameWidth / screenRatio;
 
-            diffWidth = (int) (wallpaperFrame.width() * wallpaperWin.mHScale) - screenWidth;
-            diffHeight = (int) (wallpaperFrame.height() * wallpaperWin.mVScale) - screenHeight;
+            // Note: a positive x/y offset shifts the wallpaper to the right/bottom respectively.
+            cropOffsetX = -wallpaperFrame.left + (int) ((cropZoom - 1f) * w / 2f);
+            cropOffsetY = -wallpaperFrame.top + (int) ((cropZoom - 1f) * h / 2f);
+
+            // Available width or height for parallax
+            diffWidth = (int) ((frameWidth - w) * wallpaperWin.mHScale);
+            diffHeight = (int) ((frameHeight - h) * wallpaperWin.mVScale);
         } else {
             wallpaperFrame = wallpaperWin.getFrame();
             cropZoom = 1f;
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index ccd9bd0..b2bdaa3 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -24,16 +24,17 @@
 #include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <powermanager/PowerHalController.h>
+#include <powermanager/PowerHintSessionWrapper.h>
 #include <utils/Log.h>
 
 #include <unordered_map>
 
 #include "jni.h"
 
-using aidl::android::hardware::power::IPowerHintSession;
 using aidl::android::hardware::power::SessionHint;
 using aidl::android::hardware::power::SessionMode;
 using aidl::android::hardware::power::WorkDuration;
+using android::power::PowerHintSessionWrapper;
 
 using android::base::StringPrintf;
 
@@ -49,7 +50,7 @@
 } gWorkDurationInfo;
 
 static power::PowerHalController gPowerHalController;
-static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap;
+static std::unordered_map<jlong, std::shared_ptr<PowerHintSessionWrapper>> gSessionMap;
 static std::mutex gSessionMapLock;
 
 static int64_t getHintSessionPreferredRate() {
@@ -76,45 +77,45 @@
 }
 
 static void pauseHintSession(JNIEnv* env, int64_t session_ptr) {
-    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->pause();
 }
 
 static void resumeHintSession(JNIEnv* env, int64_t session_ptr) {
-    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->resume();
 }
 
 static void closeHintSession(JNIEnv* env, int64_t session_ptr) {
-    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->close();
     std::unique_lock<std::mutex> sessionLock(gSessionMapLock);
     gSessionMap.erase(session_ptr);
 }
 
 static void updateTargetWorkDuration(int64_t session_ptr, int64_t targetDurationNanos) {
-    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->updateTargetWorkDuration(targetDurationNanos);
 }
 
 static void reportActualWorkDuration(int64_t session_ptr,
                                      const std::vector<WorkDuration>& actualDurations) {
-    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->reportActualWorkDuration(actualDurations);
 }
 
 static void sendHint(int64_t session_ptr, SessionHint hint) {
-    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->sendHint(hint);
 }
 
 static void setThreads(int64_t session_ptr, const std::vector<int32_t>& threadIds) {
-    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->setThreads(threadIds);
 }
 
 static void setMode(int64_t session_ptr, SessionMode mode, bool enabled) {
-    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
     appSession->setMode(mode, enabled);
 }
 
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 027337f..f5e1e41 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -113,8 +113,8 @@
 
     /** Creates intent that is ot be invoked to cancel an in-progress UI session. */
     public Intent createCancelIntent(IBinder requestId, String packageName) {
-        return IntentFactory.createCancelUiIntent(requestId, /*shouldShowCancellationUi=*/ true,
-                packageName);
+        return IntentFactory.createCancelUiIntent(mContext, requestId,
+                /*shouldShowCancellationUi=*/ true, packageName);
     }
 
     /**
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 12f4407..6aeb4fd5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -225,7 +225,7 @@
 
         synchronized (mLock) {
             PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
-            if (Flags.devicePolicySizeTrackingEnabled() && false) {
+            if (Flags.devicePolicySizeTrackingInternalEnabled()) {
                 if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value,
                         policyDefinition, userId)) {
                     return;
@@ -350,7 +350,7 @@
             }
             PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
 
-            if (Flags.devicePolicySizeTrackingEnabled() && false) {
+            if (Flags.devicePolicySizeTrackingInternalEnabled()) {
                 decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin);
             }
 
@@ -496,7 +496,7 @@
 
         synchronized (mLock) {
             PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
-            if (Flags.devicePolicySizeTrackingEnabled() && false) {
+            if (Flags.devicePolicySizeTrackingInternalEnabled()) {
                 if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value,
                         policyDefinition, UserHandle.USER_ALL)) {
                     return;
@@ -568,7 +568,7 @@
         synchronized (mLock) {
             PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
 
-            if (Flags.devicePolicySizeTrackingEnabled() && false) {
+            if (Flags.devicePolicySizeTrackingInternalEnabled()) {
                 decreasePolicySizeForAdmin(policyState, enforcingAdmin);
             }
 
@@ -1892,7 +1892,7 @@
 
         private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer)
                 throws IOException {
-            if (Flags.devicePolicySizeTrackingEnabled() && false) {
+            if (Flags.devicePolicySizeTrackingInternalEnabled()) {
                 if (mAdminPolicySize != null) {
                     for (int i = 0; i < mAdminPolicySize.size(); i++) {
                         int userId = mAdminPolicySize.keyAt(i);
@@ -1916,7 +1916,7 @@
 
         private void writeMaxPolicySizeInner(TypedXmlSerializer serializer)
                 throws IOException {
-            if (!Flags.devicePolicySizeTrackingEnabled() || true) {
+            if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
                 return;
             }
             serializer.startTag(/* namespace= */ null, TAG_MAX_POLICY_SIZE_LIMIT);
@@ -2081,7 +2081,7 @@
 
         private void readMaxPolicySizeInner(TypedXmlPullParser parser)
                 throws XmlPullParserException, IOException {
-            if (!Flags.devicePolicySizeTrackingEnabled() || true) {
+            if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
                 return;
             }
             mPolicySizeLimit = parser.getAttributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0f97f4a..80046b60 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -27,6 +27,7 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AIRPLANE_MODE;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUTOFILL;
@@ -83,7 +84,6 @@
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER;
-import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_VPN;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WALLPAPER;
 import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI;
@@ -196,11 +196,11 @@
 import static android.app.admin.DevicePolicyManager.STATUS_DEVICE_ADMIN_NOT_SUPPORTED;
 import static android.app.admin.DevicePolicyManager.STATUS_HAS_DEVICE_OWNER;
 import static android.app.admin.DevicePolicyManager.STATUS_HAS_PAIRED;
+import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_ONLY_SYSTEM_USER;
 import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED;
 import static android.app.admin.DevicePolicyManager.STATUS_MANAGED_USERS_NOT_SUPPORTED;
 import static android.app.admin.DevicePolicyManager.STATUS_NONSYSTEM_USER_EXISTS;
 import static android.app.admin.DevicePolicyManager.STATUS_NOT_SYSTEM_USER;
-import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_ONLY_SYSTEM_USER;
 import static android.app.admin.DevicePolicyManager.STATUS_OK;
 import static android.app.admin.DevicePolicyManager.STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
 import static android.app.admin.DevicePolicyManager.STATUS_SYSTEM_USER;
@@ -12062,7 +12062,7 @@
         }
 
         if (packageList != null) {
-            if (!Flags.devicePolicySizeTrackingEnabled()) {
+            if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
                 for (String pkg : packageList) {
                     PolicySizeVerifier.enforceMaxPackageNameLength(pkg);
                 }
@@ -13771,7 +13771,7 @@
             return;
         }
 
-        if (!Flags.devicePolicySizeTrackingEnabled()) {
+        if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
             PolicySizeVerifier.enforceMaxStringLength(accountType, "account type");
         }
 
@@ -14385,7 +14385,7 @@
     public void setLockTaskPackages(ComponentName who, String callerPackageName, String[] packages)
             throws SecurityException {
         Objects.requireNonNull(packages, "packages is null");
-        if (!Flags.devicePolicySizeTrackingEnabled()) {
+        if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
             for (String pkg : packages) {
                 PolicySizeVerifier.enforceMaxPackageNameLength(pkg);
             }
@@ -24235,7 +24235,7 @@
 
     @Override
     public void setMaxPolicyStorageLimit(String callerPackageName, int storageLimit) {
-        if (!Flags.devicePolicySizeTrackingEnabled() || true) {
+        if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
             return;
         }
         CallerIdentity caller = getCallerIdentity(callerPackageName);
@@ -24247,7 +24247,7 @@
 
     @Override
     public int getMaxPolicyStorageLimit(String callerPackageName) {
-        if (!Flags.devicePolicySizeTrackingEnabled() || true) {
+        if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
             return -1;
         }
         CallerIdentity caller = getCallerIdentity(callerPackageName);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e19f08c..9d95c5b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2774,9 +2774,12 @@
         t.traceEnd();
 
         // OnDevicePersonalizationSystemService
-        t.traceBegin("StartOnDevicePersonalizationSystemService");
-        mSystemServiceManager.startService(ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS);
-        t.traceEnd();
+        if (!com.android.server.flags.Flags.enableOdpFeatureGuard()
+                || SystemProperties.getBoolean("ro.system_settings.service.odp_enabled", true)) {
+            t.traceBegin("StartOnDevicePersonalizationSystemService");
+            mSystemServiceManager.startService(ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS);
+            t.traceEnd();
+        }
 
         // Profiling
         if (android.server.Flags.telemetryApisService()) {
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index fc2eb26..c0d988d 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -20,27 +20,63 @@
 import android.companion.virtual.VirtualDeviceManager
 import android.os.Handler
 import android.os.UserHandle
+import android.permission.flags.Flags
 import android.util.ArrayMap
 import android.util.ArraySet
+import android.util.LongSparseArray
+import android.util.Slog
+import android.util.SparseArray
 import android.util.SparseBooleanArray
 import android.util.SparseIntArray
 import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.util.IntPair
 import com.android.server.appop.AppOpsCheckingServiceInterface
 import com.android.server.appop.AppOpsCheckingServiceInterface.AppOpsModeChangedListener
 import com.android.server.permission.access.AccessCheckingService
 import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.DevicePermissionUri
+import com.android.server.permission.access.GetStateScope
 import com.android.server.permission.access.PackageUri
+import com.android.server.permission.access.PermissionUri
 import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.appop.AppOpModes.MODE_ALLOWED
+import com.android.server.permission.access.appop.AppOpModes.MODE_FOREGROUND
+import com.android.server.permission.access.appop.AppOpModes.MODE_IGNORED
 import com.android.server.permission.access.collection.forEachIndexed
 import com.android.server.permission.access.collection.set
+import com.android.server.permission.access.permission.AppIdPermissionPolicy
+import com.android.server.permission.access.permission.DevicePermissionPolicy
+import com.android.server.permission.access.permission.PermissionFlags
+import com.android.server.permission.access.permission.PermissionService
 
 class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingServiceInterface {
     private val packagePolicy =
         service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) as PackageAppOpPolicy
     private val appIdPolicy =
         service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy
+    private val permissionPolicy =
+        service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy
+    private val devicePermissionPolicy =
+        service.getSchemePolicy(UidUri.SCHEME, DevicePermissionUri.SCHEME) as DevicePermissionPolicy
 
     private val context = service.context
+
+    // Maps appop code to its runtime permission
+    private val runtimeAppOpToPermissionNames = SparseArray<String>()
+
+    // Maps runtime permission to its appop codes
+    private val runtimePermissionNameToAppOp = ArrayMap<String, Int>()
+
+    private var foregroundableOps = SparseBooleanArray()
+
+    /* Maps foreground permissions to their background permission. Background permissions aren't
+    required to be runtime */
+    private val foregroundToBackgroundPermissionName = ArrayMap<String, String>()
+
+    /* Maps background permissions to their foreground permissions. Background permissions aren't
+    required to be runtime */
+    private val backgroundToForegroundPermissionNames = ArrayMap<String, ArraySet<String>>()
+
     private lateinit var handler: Handler
 
     @Volatile private var listeners = ArraySet<AppOpsModeChangedListener>()
@@ -69,11 +105,60 @@
     }
 
     override fun systemReady() {
-        // Not implemented because upgrades are handled automatically.
+        if (Flags.runtimePermissionAppopsMappingEnabled()) {
+            createPermissionAppOpMapping()
+            val permissionListener = OnPermissionFlagsChangedListener()
+            permissionPolicy.addOnPermissionFlagsChangedListener(permissionListener)
+            devicePermissionPolicy.addOnPermissionFlagsChangedListener(permissionListener)
+        }
+    }
+
+    private fun createPermissionAppOpMapping() {
+        val permissions = service.getState { with(permissionPolicy) { getPermissions() } }
+
+        for (appOpCode in 0 until AppOpsManager._NUM_OP) {
+            AppOpsManager.opToPermission(appOpCode)?.let { permissionName ->
+                // Multiple ops might map to a single permission but only one is considered the
+                // runtime appop calculations.
+                if (appOpCode == AppOpsManager.permissionToOpCode(permissionName)) {
+                    val permission = permissions[permissionName]!!
+                    if (permission.isRuntime) {
+                        runtimePermissionNameToAppOp[permissionName] = appOpCode
+                        runtimeAppOpToPermissionNames[appOpCode] = permissionName
+                        permission.permissionInfo.backgroundPermission?.let {
+                            backgroundPermissionName ->
+                            // Note: background permission may not be runtime,
+                            // e.g. microphone/camera.
+                            foregroundableOps[appOpCode] = true
+                            foregroundToBackgroundPermissionName[permissionName] =
+                                backgroundPermissionName
+                            backgroundToForegroundPermissionNames
+                                .getOrPut(backgroundPermissionName, ::ArraySet)
+                                .add(permissionName)
+                        }
+                    }
+                }
+            }
+        }
     }
 
     override fun getNonDefaultUidModes(uid: Int, persistentDeviceId: String): SparseIntArray {
-        return opNameMapToOpSparseArray(getUidModes(uid))
+        val appId = UserHandle.getAppId(uid)
+        val userId = UserHandle.getUserId(uid)
+        service.getState {
+            val modes =
+                with(appIdPolicy) { opNameMapToOpSparseArray(getAppOpModes(appId, userId)?.map) }
+            if (Flags.runtimePermissionAppopsMappingEnabled()) {
+                runtimePermissionNameToAppOp.forEachIndexed { _, permissionName, appOpCode ->
+                    val mode = getUidModeFromPermissionState(appId, userId, permissionName)
+                    if (mode != AppOpsManager.opToDefaultMode(appOpCode)) {
+                        modes[appOpCode] = mode
+                    }
+                }
+            }
+
+            return modes
+        }
     }
 
     override fun getNonDefaultPackageModes(packageName: String, userId: Int): SparseIntArray {
@@ -84,7 +169,13 @@
         val appId = UserHandle.getAppId(uid)
         val userId = UserHandle.getUserId(uid)
         val opName = AppOpsManager.opToPublicName(op)
-        return service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } }
+        val permissionName = runtimeAppOpToPermissionNames[op]
+
+        return if (!Flags.runtimePermissionAppopsMappingEnabled() || permissionName == null) {
+            service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } }
+        } else {
+            service.getState { getUidModeFromPermissionState(appId, userId, permissionName) }
+        }
     }
 
     private fun getUidModes(uid: Int): ArrayMap<String, Int>? {
@@ -93,13 +184,66 @@
         return service.getState { with(appIdPolicy) { getAppOpModes(appId, userId) } }?.map
     }
 
-    override fun setUidMode(uid: Int, persistentDeviceId: String, op: Int, mode: Int): Boolean {
+    private fun GetStateScope.getUidModeFromPermissionState(
+        appId: Int,
+        userId: Int,
+        permissionName: String
+    ): Int {
+        val permissionFlags =
+            with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) }
+        val backgroundPermissionName = foregroundToBackgroundPermissionName[permissionName]
+        val backgroundPermissionFlags =
+            if (backgroundPermissionName != null) {
+                with(permissionPolicy) {
+                    getPermissionFlags(appId, userId, backgroundPermissionName)
+                }
+            } else {
+                PermissionFlags.RUNTIME_GRANTED
+            }
+        val result = evaluateModeFromPermissionFlags(permissionFlags, backgroundPermissionFlags)
+        if (result != MODE_IGNORED) {
+            return result
+        }
+
+        val fullerPermissionName =
+            PermissionService.getFullerPermission(permissionName) ?: return result
+        return getUidModeFromPermissionState(appId, userId, fullerPermissionName)
+    }
+
+    private fun evaluateModeFromPermissionFlags(
+        foregroundFlags: Int,
+        backgroundFlags: Int = PermissionFlags.RUNTIME_GRANTED
+    ): Int =
+        if (PermissionFlags.isAppOpGranted(foregroundFlags)) {
+            if (PermissionFlags.isAppOpGranted(backgroundFlags)) {
+                MODE_ALLOWED
+            } else {
+                MODE_FOREGROUND
+            }
+        } else {
+            MODE_IGNORED
+        }
+
+    override fun setUidMode(uid: Int, persistentDeviceId: String, code: Int, mode: Int): Boolean {
+        if (
+            Flags.runtimePermissionAppopsMappingEnabled() && code in runtimeAppOpToPermissionNames
+        ) {
+            Slog.w(
+                LOG_TAG,
+                "Cannot set UID mode for runtime permission app op, uid = $uid," +
+                    " code = ${AppOpsManager.opToName(code)}," +
+                    " mode = ${AppOpsManager.modeToName(mode)}",
+                RuntimeException()
+            )
+            return false
+        }
+
         val appId = UserHandle.getAppId(uid)
         val userId = UserHandle.getUserId(uid)
-        val opName = AppOpsManager.opToPublicName(op)
-        var wasChanged = false
+        val appOpName = AppOpsManager.opToPublicName(code)
+        var wasChanged: Boolean
         service.mutateState {
-            wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, opName, mode) }
+            wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, appOpName, mode) }
         }
         return wasChanged
     }
@@ -114,10 +258,23 @@
     private fun getPackageModes(packageName: String, userId: Int): ArrayMap<String, Int>? =
         service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }?.map
 
-    override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
-        val opName = AppOpsManager.opToPublicName(op)
+    override fun setPackageMode(packageName: String, appOpCode: Int, mode: Int, userId: Int) {
+        val appOpName = AppOpsManager.opToPublicName(appOpCode)
+
+        if (
+            Flags.runtimePermissionAppopsMappingEnabled() &&
+                appOpCode in runtimeAppOpToPermissionNames
+        ) {
+            Slog.w(
+                LOG_TAG,
+                "(packageName=$packageName, userId=$userId)'s appop state" +
+                    " for runtime op $appOpName should not be set directly.",
+                RuntimeException()
+            )
+            return
+        }
         service.mutateState {
-            with(packagePolicy) { setAppOpMode(packageName, userId, opName, mode) }
+            with(packagePolicy) { setAppOpMode(packageName, userId, appOpName, mode) }
         }
     }
 
@@ -128,7 +285,7 @@
     }
 
     override fun removePackage(packageName: String, userId: Int): Boolean {
-        var wasChanged = false
+        var wasChanged: Boolean
         service.mutateState {
             wasChanged = with(packagePolicy) { removeAppOpModes(packageName, userId) }
         }
@@ -158,6 +315,13 @@
                     this[AppOpsManager.strOpToOp(op)] = true
                 }
             }
+            if (Flags.runtimePermissionAppopsMappingEnabled()) {
+                foregroundableOps.forEachIndexed { _, op, _ ->
+                    if (getUidMode(uid, persistentDeviceId, op) == AppOpsManager.MODE_FOREGROUND) {
+                        this[op] = true
+                    }
+                }
+            }
         }
     }
 
@@ -168,6 +332,13 @@
                     this[AppOpsManager.strOpToOp(op)] = true
                 }
             }
+            if (Flags.runtimePermissionAppopsMappingEnabled()) {
+                foregroundableOps.forEachIndexed { _, op, _ ->
+                    if (getPackageMode(packageName, op, userId) == AppOpsManager.MODE_FOREGROUND) {
+                        this[op] = true
+                    }
+                }
+            }
         }
     }
 
@@ -189,9 +360,10 @@
         }
     }
 
-    inner class OnAppIdAppOpModeChangedListener : AppIdAppOpPolicy.OnAppOpModeChangedListener() {
+    private inner class OnAppIdAppOpModeChangedListener :
+        AppIdAppOpPolicy.OnAppOpModeChangedListener() {
         // (uid, appOpCode) -> newMode
-        val pendingChanges = ArrayMap<Pair<Int, Int>, Int>()
+        private val pendingChanges = LongSparseArray<Int>()
 
         override fun onAppOpModeChanged(
             appId: Int,
@@ -202,7 +374,7 @@
         ) {
             val uid = UserHandle.getUid(userId, appId)
             val appOpCode = AppOpsManager.strOpToOp(appOpName)
-            val key = Pair(uid, appOpCode)
+            val key = IntPair.of(uid, appOpCode)
 
             pendingChanges[key] = newMode
         }
@@ -211,13 +383,15 @@
             val listenersLocal = listeners
             pendingChanges.forEachIndexed { _, key, mode ->
                 listenersLocal.forEachIndexed { _, listener ->
-                    val uid = key.first
-                    val appOpCode = key.second
+                    val uid = IntPair.first(key)
+                    val appOpCode = IntPair.second(key)
 
-                    listener.onUidModeChanged(uid,
+                    listener.onUidModeChanged(
+                        uid,
                         appOpCode,
                         mode,
-                        VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)
+                        VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
+                    )
                 }
             }
 
@@ -228,7 +402,7 @@
     private inner class OnPackageAppOpModeChangedListener :
         PackageAppOpPolicy.OnAppOpModeChangedListener() {
         // (packageName, userId, appOpCode) -> newMode
-        val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>()
+        private val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>()
 
         override fun onAppOpModeChanged(
             packageName: String,
@@ -258,4 +432,130 @@
             pendingChanges.clear()
         }
     }
+
+    private inner class OnPermissionFlagsChangedListener :
+        AppIdPermissionPolicy.OnPermissionFlagsChangedListener,
+        DevicePermissionPolicy.OnDevicePermissionFlagsChangedListener {
+        // (uid, deviceId, appOpCode) -> newMode
+        private val pendingChanges = ArrayMap<Triple<Int, String, Int>, Int>()
+
+        override fun onPermissionFlagsChanged(
+            appId: Int,
+            userId: Int,
+            permissionName: String,
+            oldFlags: Int,
+            newFlags: Int
+        ) {
+            onDevicePermissionFlagsChanged(
+                appId,
+                userId,
+                VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT,
+                permissionName,
+                oldFlags,
+                newFlags
+            )
+        }
+
+        override fun onDevicePermissionFlagsChanged(
+            appId: Int,
+            userId: Int,
+            deviceId: String,
+            permissionName: String,
+            oldFlags: Int,
+            newFlags: Int
+        ) {
+            backgroundToForegroundPermissionNames[permissionName]?.let { foregroundPermissions ->
+                // This is a background permission; there may be multiple foreground permissions
+                // affected.
+                foregroundPermissions.forEachIndexed { _, foregroundPermissionName ->
+                    runtimePermissionNameToAppOp[foregroundPermissionName]?.let { appOpCode ->
+                        val foregroundPermissionFlags =
+                            getPermissionFlags(appId, userId, foregroundPermissionName)
+                        addPendingChangedModeIfNeeded(
+                            appId,
+                            userId,
+                            deviceId,
+                            appOpCode,
+                            foregroundPermissionFlags,
+                            oldFlags,
+                            foregroundPermissionFlags,
+                            newFlags
+                        )
+                    }
+                }
+            }
+                ?: foregroundToBackgroundPermissionName[permissionName]?.let { backgroundPermission
+                    ->
+                    runtimePermissionNameToAppOp[permissionName]?.let { appOpCode ->
+                        val backgroundPermissionFlags =
+                            getPermissionFlags(appId, userId, backgroundPermission)
+                        addPendingChangedModeIfNeeded(
+                            appId,
+                            userId,
+                            deviceId,
+                            appOpCode,
+                            oldFlags,
+                            backgroundPermissionFlags,
+                            newFlags,
+                            backgroundPermissionFlags
+                        )
+                    }
+                }
+                ?: runtimePermissionNameToAppOp[permissionName]?.let { appOpCode ->
+                    addPendingChangedModeIfNeeded(
+                        appId,
+                        userId,
+                        deviceId,
+                        appOpCode,
+                        oldFlags,
+                        PermissionFlags.RUNTIME_GRANTED,
+                        newFlags,
+                        PermissionFlags.RUNTIME_GRANTED
+                    )
+                }
+        }
+
+        private fun getPermissionFlags(appId: Int, userId: Int, permissionName: String): Int =
+            service.getState {
+                with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) }
+            }
+
+        private fun addPendingChangedModeIfNeeded(
+            appId: Int,
+            userId: Int,
+            deviceId: String,
+            appOpCode: Int,
+            oldForegroundFlags: Int,
+            oldBackgroundFlags: Int,
+            newForegroundFlags: Int,
+            newBackgroundFlags: Int,
+        ) {
+            val oldMode = evaluateModeFromPermissionFlags(oldForegroundFlags, oldBackgroundFlags)
+            val newMode = evaluateModeFromPermissionFlags(newForegroundFlags, newBackgroundFlags)
+
+            if (oldMode != newMode) {
+                val uid = UserHandle.getUid(userId, appId)
+                pendingChanges[Triple(uid, deviceId, appOpCode)] = newMode
+            }
+        }
+
+        override fun onStateMutated() {
+            val listenersLocal = listeners
+            pendingChanges.forEachIndexed { _, key, mode ->
+                listenersLocal.forEachIndexed { _, listener ->
+                    val uid = key.first
+                    val deviceId = key.second
+                    val appOpCode = key.third
+
+                    listener.onUidModeChanged(uid, appOpCode, mode, deviceId)
+                }
+            }
+
+            pendingChanges.clear()
+        }
+    }
+
+    companion object {
+        private val LOG_TAG = AppOpService::class.java.simpleName
+    }
 }
diff --git a/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt
new file mode 100644
index 0000000..827dd0e
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.collection
+
+import android.util.LongSparseArray
+
+inline fun <T> LongSparseArray<T>.allIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> LongSparseArray<T>.anyIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun <T> LongSparseArray<T>.forEachIndexed(action: (Int, Long, T) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <T> LongSparseArray<T>.forEachReversedIndexed(action: (Int, Long, T) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun <T> LongSparseArray<T>.getOrPut(key: Long, defaultValue: () -> T): T {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        valueAt(index)
+    } else {
+        defaultValue().also { put(key, it) }
+    }
+}
+
+inline val <T> LongSparseArray<T>.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> LongSparseArray<T>.minusAssign(key: Long) {
+    delete(key)
+}
+
+inline fun <T> LongSparseArray<T>.noneIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun <T> LongSparseArray<T>.removeAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline fun <T> LongSparseArray<T>.retainAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline val <T> LongSparseArray<T>.size: Int
+    get() = size()
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> LongSparseArray<T>.set(key: Long, value: T) {
+    put(key, value)
+}
diff --git a/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt
new file mode 100644
index 0000000..a582431
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.collection
+
+import android.util.SparseIntArray
+
+inline fun SparseIntArray.allIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+inline fun SparseIntArray.anyIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return true
+        }
+    }
+    return false
+}
+
+inline fun SparseIntArray.forEachIndexed(action: (Int, Int, Int) -> Unit) {
+    for (index in 0 until size) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun SparseIntArray.forEachReversedIndexed(action: (Int, Int, Int) -> Unit) {
+    for (index in lastIndex downTo 0) {
+        action(index, keyAt(index), valueAt(index))
+    }
+}
+
+inline fun SparseIntArray.getOrPut(key: Int, defaultValue: () -> Int): Int {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        valueAt(index)
+    } else {
+        defaultValue().also { put(key, it) }
+    }
+}
+
+inline val SparseIntArray.lastIndex: Int
+    get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseIntArray.minusAssign(key: Int) {
+    delete(key)
+}
+
+inline fun SparseIntArray.noneIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+    forEachIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            return false
+        }
+    }
+    return true
+}
+
+fun SparseIntArray.remove(key: Int) {
+    delete(key)
+}
+
+fun SparseIntArray.remove(key: Int, defaultValue: Int): Int {
+    val index = indexOfKey(key)
+    return if (index >= 0) {
+        val oldValue = valueAt(index)
+        removeAt(index)
+        oldValue
+    } else {
+        defaultValue
+    }
+}
+
+inline fun SparseIntArray.removeAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+inline fun SparseIntArray.retainAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+    var isChanged = false
+    forEachReversedIndexed { index, key, value ->
+        if (!predicate(index, key, value)) {
+            removeAt(index)
+            isChanged = true
+        }
+    }
+    return isChanged
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseIntArray.set(key: Int, value: Int) {
+    put(key, value)
+}
+
+inline val SparseIntArray.size: Int
+    get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 4b086b3..67df67f 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -227,25 +227,59 @@
             if (isRequestedBySystemPackage) {
                 return@forEach
             }
-            val oldFlags = getPermissionFlags(appId, userId, permissionName)
-            var newFlags = oldFlags andInv PermissionFlags.UPGRADE_EXEMPT
-            val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
-            newFlags =
-                if (permission.isHardRestricted && !isExempt) {
-                    newFlags or PermissionFlags.RESTRICTION_REVOKED
-                } else {
-                    newFlags andInv PermissionFlags.RESTRICTION_REVOKED
-                }
-            newFlags =
-                if (permission.isSoftRestricted && !isExempt) {
-                    newFlags or PermissionFlags.SOFT_RESTRICTED
-                } else {
-                    newFlags andInv PermissionFlags.SOFT_RESTRICTED
-                }
-            setPermissionFlags(appId, userId, permissionName, newFlags)
+            updatePermissionExemptFlags(
+                appId,
+                userId,
+                permission,
+                PermissionFlags.UPGRADE_EXEMPT,
+                0
+            )
         }
     }
 
+    fun MutateStateScope.updatePermissionExemptFlags(
+        appId: Int,
+        userId: Int,
+        permission: Permission,
+        exemptFlagMask: Int,
+        exemptFlagValues: Int
+    ) {
+        val permissionName = permission.name
+        val oldFlags = getPermissionFlags(appId, userId, permissionName)
+        var newFlags = (oldFlags andInv exemptFlagMask) or (exemptFlagValues and exemptFlagMask)
+        if (oldFlags == newFlags) {
+            return
+        }
+        val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
+        if (permission.isHardRestricted && !isExempt) {
+            newFlags = newFlags or PermissionFlags.RESTRICTION_REVOKED
+            // If the permission was policy fixed as granted but it is no longer on any of the
+            // allowlists we need to clear the policy fixed flag as allowlisting trumps policy i.e.
+            // policy cannot grant a non grantable permission.
+            if (PermissionFlags.isPermissionGranted(oldFlags)) {
+                newFlags = newFlags andInv PermissionFlags.POLICY_FIXED
+            }
+        } else {
+            newFlags = newFlags andInv PermissionFlags.RESTRICTION_REVOKED
+        }
+        newFlags =
+            if (
+                permission.isSoftRestricted && !isExempt &&
+                    !anyPackageInAppId(appId) {
+                        permissionName in it.androidPackage!!.requestedPermissions &&
+                            isSoftRestrictedPermissionExemptForPackage(it, permissionName)
+                    }
+            ) {
+                newFlags or PermissionFlags.SOFT_RESTRICTED
+            } else {
+                newFlags andInv PermissionFlags.SOFT_RESTRICTED
+            }
+        if (oldFlags == newFlags) {
+            return
+        }
+        setPermissionFlags(appId, userId, permissionName, newFlags)
+    }
+
     override fun MutateStateScope.onPackageUninstalled(
         packageName: String,
         appId: Int,
@@ -1118,7 +1152,12 @@
                     newFlags andInv PermissionFlags.RESTRICTION_REVOKED
                 }
             newFlags =
-                if (permission.isSoftRestricted && !isExempt) {
+                if (
+                    permission.isSoftRestricted && !isExempt &&
+                        !requestingPackageStates.anyIndexed { _, it ->
+                            isSoftRestrictedPermissionExemptForPackage(it, permissionName)
+                        }
+                ) {
                     newFlags or PermissionFlags.SOFT_RESTRICTED
                 } else {
                     newFlags andInv PermissionFlags.SOFT_RESTRICTED
@@ -1398,6 +1437,17 @@
         }
     }
 
+    // See also SoftRestrictedPermissionPolicy.mayGrantPermission()
+    private fun isSoftRestrictedPermissionExemptForPackage(
+        packageState: PackageState,
+        permissionName: String
+    ): Boolean =
+        when (permissionName) {
+            Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ->
+                packageState.androidPackage!!.targetSdkVersion >= Build.VERSION_CODES.Q
+            else -> false
+        }
+
     private inline fun MutateStateScope.anyPackageInAppId(
         appId: Int,
         state: AccessState = newState,
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
index 28889de..c5c921d 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
@@ -346,9 +346,18 @@
         return flags.hasBits(RUNTIME_GRANTED)
     }
 
-    fun isAppOpGranted(flags: Int): Boolean =
-        isPermissionGranted(flags) && !flags.hasBits(RESTRICTION_REVOKED) &&
-            !flags.hasBits(APP_OP_REVOKED)
+    fun isAppOpGranted(flags: Int): Boolean {
+        if (!isPermissionGranted(flags)) {
+            return false
+        }
+        if (flags.hasAnyBit(MASK_RESTRICTED)) {
+            return false
+        }
+        if (flags.hasBits(APP_OP_REVOKED)) {
+            return false
+        }
+        return true
+    }
 
     fun toApiFlags(flags: Int): Int {
         var apiFlags = 0
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 0704c8f..0b58543 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -88,7 +88,6 @@
 import com.android.server.pm.PackageManagerLocal
 import com.android.server.pm.UserManagerInternal
 import com.android.server.pm.UserManagerService
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils
 import com.android.server.pm.permission.LegacyPermission
 import com.android.server.pm.permission.LegacyPermissionSettings
 import com.android.server.pm.permission.LegacyPermissionState
@@ -97,7 +96,6 @@
 import com.android.server.pm.permission.PermissionManagerServiceInternal
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageState
-import com.android.server.policy.SoftRestrictedPermissionPolicy
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.util.concurrent.CompletableFuture
@@ -1006,25 +1004,14 @@
         }
 
         if (isGranted && oldFlags.hasBits(PermissionFlags.SOFT_RESTRICTED)) {
-            // TODO: Refactor SoftRestrictedPermissionPolicy.
-            val softRestrictedPermissionPolicy =
-                SoftRestrictedPermissionPolicy.forPermission(
-                    context,
-                    AndroidPackageUtils.generateAppInfoWithoutState(androidPackage),
-                    androidPackage,
-                    UserHandle.of(userId),
-                    permissionName
+            if (reportError) {
+                Slog.e(
+                    LOG_TAG,
+                    "$methodName: Cannot grant soft-restricted non-exempt permission" +
+                        " $permissionName to package $packageName"
                 )
-            if (!softRestrictedPermissionPolicy.mayGrantPermission()) {
-                if (reportError) {
-                    Slog.e(
-                        LOG_TAG,
-                        "$methodName: Cannot grant soft-restricted non-exempt permission" +
-                            " $permissionName to package $packageName"
-                    )
-                }
-                return
             }
+            return
         }
 
         val newFlags = PermissionFlags.updateRuntimePermissionGranted(oldFlags, isGranted)
@@ -1135,25 +1122,23 @@
             return emptyMap()
         }
 
-        val permissionFlagsMap =
-            service.getState {
+        service.getState {
+            val permissionFlags =
                 if (deviceId == VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT) {
                     with(policy) { getAllPermissionFlags(packageState.appId, userId) }
                 } else {
                     with(devicePolicy) {
                         getAllPermissionFlags(packageState.appId, deviceId, userId)
                     }
-                }
+                } ?: return emptyMap()
+            val permissionStates = ArrayMap<String, PermissionState>()
+            permissionFlags.forEachIndexed { _, permissionName, flags ->
+                val granted = isPermissionGranted(packageState, userId, permissionName, deviceId)
+                val apiFlags = PermissionFlags.toApiFlags(flags)
+                permissionStates[permissionName] = PermissionState(granted, apiFlags)
             }
-                ?: return emptyMap()
-
-        val permissionStates = ArrayMap<String, PermissionState>()
-        permissionFlagsMap.forEachIndexed { _, permissionName, flags ->
-            val granted = PermissionFlags.isPermissionGranted(flags)
-            val apiFlags = PermissionFlags.toApiFlags(flags)
-            permissionStates[permissionName] = PermissionState(granted, apiFlags)
+            return permissionStates
         }
-        return permissionStates
     }
 
     override fun isPermissionRevokedByPolicy(
@@ -1852,10 +1837,19 @@
         allowlistedFlags: Int,
         userId: Int
     ) {
+        var exemptMask = 0
+        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM)) {
+            exemptMask = exemptMask or PermissionFlags.SYSTEM_EXEMPT
+        }
+        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE)) {
+            exemptMask = exemptMask or PermissionFlags.UPGRADE_EXEMPT
+        }
+        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) {
+            exemptMask = exemptMask or PermissionFlags.INSTALLER_EXEMPT
+        }
+
         service.mutateState {
             with(policy) {
-                val permissionsFlags = getUidPermissionFlags(appId, userId) ?: return@mutateState
-
                 val permissions = getPermissions()
                 androidPackage.requestedPermissions.forEachIndexed { _, requestedPermission ->
                     val permission = permissions[requestedPermission]
@@ -1863,81 +1857,8 @@
                         return@forEachIndexed
                     }
 
-                    val oldFlags = permissionsFlags[requestedPermission] ?: 0
-                    val wasGranted = PermissionFlags.isPermissionGranted(oldFlags)
-
-                    var newFlags = oldFlags
-                    var mask = 0
-                    var allowlistFlagsCopy = allowlistedFlags
-                    while (allowlistFlagsCopy != 0) {
-                        val flag = 1 shl allowlistFlagsCopy.countTrailingZeroBits()
-                        allowlistFlagsCopy = allowlistFlagsCopy and flag.inv()
-                        when (flag) {
-                            PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM -> {
-                                mask = mask or PermissionFlags.SYSTEM_EXEMPT
-                                newFlags =
-                                    if (permissionNames.contains(requestedPermission)) {
-                                        newFlags or PermissionFlags.SYSTEM_EXEMPT
-                                    } else {
-                                        newFlags andInv PermissionFlags.SYSTEM_EXEMPT
-                                    }
-                            }
-                            PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE -> {
-                                mask = mask or PermissionFlags.UPGRADE_EXEMPT
-                                newFlags =
-                                    if (permissionNames.contains(requestedPermission)) {
-                                        newFlags or PermissionFlags.UPGRADE_EXEMPT
-                                    } else {
-                                        newFlags andInv PermissionFlags.UPGRADE_EXEMPT
-                                    }
-                            }
-                            PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER -> {
-                                mask = mask or PermissionFlags.INSTALLER_EXEMPT
-                                newFlags =
-                                    if (permissionNames.contains(requestedPermission)) {
-                                        newFlags or PermissionFlags.INSTALLER_EXEMPT
-                                    } else {
-                                        newFlags andInv PermissionFlags.INSTALLER_EXEMPT
-                                    }
-                            }
-                        }
-                    }
-
-                    if (oldFlags == newFlags) {
-                        return@forEachIndexed
-                    }
-
-                    val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
-
-                    // If the permission is policy fixed as granted but it is no longer
-                    // on any of the allowlists we need to clear the policy fixed flag
-                    // as allowlisting trumps policy i.e. policy cannot grant a non
-                    // grantable permission.
-                    if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED)) {
-                        if (!isExempt && wasGranted) {
-                            mask = mask or PermissionFlags.POLICY_FIXED
-                            newFlags = newFlags andInv PermissionFlags.POLICY_FIXED
-                        }
-                    }
-
-                    newFlags =
-                        if (permission.isHardRestricted && !isExempt) {
-                            newFlags or PermissionFlags.RESTRICTION_REVOKED
-                        } else {
-                            newFlags andInv PermissionFlags.RESTRICTION_REVOKED
-                        }
-                    newFlags =
-                        if (permission.isSoftRestricted && !isExempt) {
-                            newFlags or PermissionFlags.SOFT_RESTRICTED
-                        } else {
-                            newFlags andInv PermissionFlags.SOFT_RESTRICTED
-                        }
-                    mask =
-                        mask or
-                            PermissionFlags.RESTRICTION_REVOKED or
-                            PermissionFlags.SOFT_RESTRICTED
-
-                    updatePermissionFlags(appId, userId, requestedPermission, mask, newFlags)
+                    var exemptFlags = if (requestedPermission in permissionNames) exemptMask else 0
+                    updatePermissionExemptFlags(appId, userId, permission, exemptMask, exemptFlags)
                 }
             }
         }
@@ -2905,5 +2826,8 @@
             } else {
                 emptySet<String>()
             }
+
+        fun getFullerPermission(permissionName: String): String? =
+            FULLER_PERMISSIONS[permissionName]
     }
 }
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
index cde46ab..96753b6 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
@@ -233,24 +233,6 @@
             .isEqualTo(expectedNewFlags)
     }
 
-    @Test
-    fun testOnPackageInstalled_restrictedPermissionsIsExempted_clearsRestrictionFlags() {
-        val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.INSTALLER_EXEMPT
-        testOnPackageInstalled(
-            oldFlags,
-            permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED
-        ) {}
-        val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
-        val expectedNewFlags = PermissionFlags.INSTALLER_EXEMPT
-        assertWithMessage(
-            "After onPackageInstalled() is called for a non-system app that requests a runtime" +
-                " soft restricted permission that is exempted. The actual permission flags" +
-                " $actualFlags should match the expected flags $expectedNewFlags"
-        )
-            .that(actualFlags)
-            .isEqualTo(expectedNewFlags)
-    }
-
     private fun testOnPackageInstalled(
         oldFlags: Int,
         permissionInfoFlags: Int = 0,
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
new file mode 100644
index 0000000..e94b8ad
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
@@ -0,0 +1,56 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "RollbackPackageHealthObserverTests",
+
+    srcs: [
+        "*.java",
+    ],
+
+    static_libs: [
+        "androidx.test.runner",
+        "mockito-target-extended-minus-junit4",
+        "services.core",
+        "truth",
+        "flag-junit",
+    ],
+
+    libs: [
+        "android.test.mock",
+        "android.test.base",
+        "android.test.runner",
+    ],
+
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+
+    certificate: "platform",
+    platform_apis: true,
+    test_suites: [
+        "device-tests",
+        "automotive-tests",
+    ],
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml
new file mode 100644
index 0000000..c52dbde
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.server.rollback">
+
+    <uses-sdk android:targetSdkVersion="35" />
+
+    <application android:testOnly="true"
+                 android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.server.rollback"
+        android:label="Frameworks Rollback Package Health Observer test" />
+</manifest>
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml
new file mode 100644
index 0000000..635183c
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Runs Rollback Package Health Observer Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="RollbackPackageHealthObserverTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="RollbackPackageHealthObserverTests" />
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.server.rollback" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
index e42bdad..6ac56bf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
@@ -1,7 +1,7 @@
 {
   "postsubmit": [
     {
-      "name": "FrameworksMockingServicesTests",
+      "name": "RollbackPackageHealthObserverTests",
       "options": [
         {
           "include-filter": "com.android.server.rollback"
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
new file mode 100644
index 0000000..7ecc7fd
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -0,0 +1,640 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wallpaper;
+
+import static android.app.WallpaperManager.LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
+import static android.app.WallpaperManager.PORTRAIT;
+import static android.app.WallpaperManager.SQUARE_LANDSCAPE;
+import static android.app.WallpaperManager.SQUARE_PORTRAIT;
+import static android.app.WallpaperManager.getOrientation;
+import static android.app.WallpaperManager.getRotatedOrientation;
+
+import static com.android.window.flags.Flags.FLAG_MULTI_CROP;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.SparseArray;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Unit tests for the most important helpers of {@link WallpaperCropper}, in particular
+ * {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)}.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(FLAG_MULTI_CROP)
+public class WallpaperCropperTest {
+
+    @Mock
+    private WallpaperDisplayHelper mWallpaperDisplayHelper;
+    private WallpaperCropper mWallpaperCropper;
+
+    private static final Point PORTRAIT_ONE = new Point(500, 800);
+    private static final Point PORTRAIT_TWO = new Point(400, 1000);
+    private static final Point PORTRAIT_THREE = new Point(2000, 800);
+    private static final Point PORTRAIT_FOUR = new Point(1600, 1000);
+
+    private static final Point SQUARE_PORTRAIT_ONE = new Point(1000, 800);
+    private static final Point SQUARE_LANDSCAPE_ONE = new Point(800, 1000);
+
+    /**
+     * Common device: a single screen of portrait/landscape orientation
+     */
+    private static final List<Point> STANDARD_DISPLAY = List.of(PORTRAIT_ONE);
+
+    /** 1: folded: portrait, unfolded: square with w < h */
+    private static final List<Point> FOLDABLE_ONE = List.of(PORTRAIT_ONE, SQUARE_PORTRAIT_ONE);
+
+    /** 2: folded: portrait, unfolded: square with w > h */
+    private static final List<Point> FOLDABLE_TWO = List.of(PORTRAIT_TWO, SQUARE_LANDSCAPE_ONE);
+
+    /** 3: folded: square with w < h, unfolded: portrait */
+    private static final List<Point> FOLDABLE_THREE = List.of(SQUARE_PORTRAIT_ONE, PORTRAIT_THREE);
+
+    /** 4: folded: square with w > h, unfolded: portrait */
+    private static final List<Point> FOLDABLE_FOUR = List.of(SQUARE_LANDSCAPE_ONE, PORTRAIT_FOUR);
+
+    /**
+     * List of different sets of displays for foldable devices. Foldable devices have two displays:
+     * a folded (smaller) unfolded (larger).
+     */
+    private static final List<List<Point>> ALL_FOLDABLE_DISPLAYS = List.of(
+            FOLDABLE_ONE, FOLDABLE_TWO, FOLDABLE_THREE, FOLDABLE_FOUR);
+
+    private SparseArray<Point> mDisplaySizes = new SparseArray<>();
+    private int mFolded = ORIENTATION_UNKNOWN;
+    private int mFoldedRotated = ORIENTATION_UNKNOWN;
+    private int mUnfolded = ORIENTATION_UNKNOWN;
+    private int mUnfoldedRotated = ORIENTATION_UNKNOWN;
+
+    private static final List<Integer> ALL_MODES = List.of(
+            WallpaperCropper.ADD, WallpaperCropper.REMOVE, WallpaperCropper.BALANCE);
+
+    @Before
+    public void setUp() {
+        initMocks(this);
+        mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
+    }
+
+    private void setUpWithDisplays(List<Point> displaySizes) {
+        mDisplaySizes = new SparseArray<>();
+        displaySizes.forEach(size -> {
+            mDisplaySizes.put(getOrientation(size), size);
+            Point rotated = new Point(size.y, size.x);
+            mDisplaySizes.put(getOrientation(rotated), rotated);
+        });
+        when(mWallpaperDisplayHelper.getDefaultDisplaySizes()).thenReturn(mDisplaySizes);
+        if (displaySizes.size() == 2) {
+            Point largestDisplay = displaySizes.stream().max(
+                    Comparator.comparingInt(p -> p.x * p.y)).get();
+            Point smallestDisplay = displaySizes.stream().min(
+                    Comparator.comparingInt(p -> p.x * p.y)).get();
+            mUnfolded = getOrientation(largestDisplay);
+            mFolded = getOrientation(smallestDisplay);
+            mUnfoldedRotated = getRotatedOrientation(mUnfolded);
+            mFoldedRotated = getRotatedOrientation(mFolded);
+        }
+        doAnswer(invocation -> getFoldedOrientation(invocation.getArgument(0)))
+                .when(mWallpaperDisplayHelper).getFoldedOrientation(anyInt());
+        doAnswer(invocation -> getUnfoldedOrientation(invocation.getArgument(0)))
+                .when(mWallpaperDisplayHelper).getUnfoldedOrientation(anyInt());
+    }
+
+    private int getFoldedOrientation(int orientation) {
+        if (orientation == ORIENTATION_UNKNOWN) return ORIENTATION_UNKNOWN;
+        if (orientation == mUnfolded) return mFolded;
+        if (orientation == mUnfoldedRotated) return mFoldedRotated;
+        return ORIENTATION_UNKNOWN;
+    }
+
+    private int getUnfoldedOrientation(int orientation) {
+        if (orientation == ORIENTATION_UNKNOWN) return ORIENTATION_UNKNOWN;
+        if (orientation == mFolded) return mUnfolded;
+        if (orientation == mFoldedRotated) return mUnfoldedRotated;
+        return ORIENTATION_UNKNOWN;
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#noParallax} successfully removes the parallax in a simple
+     * case, removing the right or left part depending on the "rtl" argument.
+     */
+    @Test
+    public void testNoParallax_noScale() {
+        Point displaySize = new Point(1000, 1000);
+        Point bitmapSize = new Point(1200, 1000);
+        Point expectedCropSize = new Point(1000, 1000);
+        Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+        assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+                .isEqualTo(leftOf(crop, expectedCropSize));
+        assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+                .isEqualTo(rightOf(crop, expectedCropSize));
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#noParallax} correctly takes zooming into account.
+     */
+    @Test
+    public void testNoParallax_withScale() {
+        Point displaySize = new Point(1000, 1000);
+        Point bitmapSize = new Point(600, 500);
+        Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+        Point expectedCropSize = new Point(500, 500);
+        assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+                .isEqualTo(leftOf(crop, expectedCropSize));
+        assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+                .isEqualTo(rightOf(crop, expectedCropSize));
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#noParallax} correctly removes parallax when the image is
+     * cropped, i.e. when the crop rectangle is not the full bitmap.
+     */
+    @Test
+    public void testNoParallax_withScaleAndCrop() {
+        Point displaySize = new Point(1000, 1000);
+        Point bitmapSize = new Point(2000, 2000);
+        Rect crop = new Rect(300, 1000, 900, 1500);
+        Point expectedCropSize = new Point(500, 500);
+        assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+                .isEqualTo(leftOf(crop, expectedCropSize));
+        assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+                .isEqualTo(rightOf(crop, expectedCropSize));
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getAdjustedCrop} does nothing when the crop has the same
+     * width/height ratio than the screen.
+     */
+    @Test
+    public void testGetAdjustedCrop_noOp() {
+        Point displaySize = new Point(1000, 1000);
+
+        for (Point bitmapSize: List.of(
+                new Point(1000, 1000),
+                new Point(2000, 2000),
+                new Point(500, 500))) {
+            for (Rect crop: List.of(
+                    new Rect(0, 0, bitmapSize.x, bitmapSize.y),
+                    new Rect(100, 200, bitmapSize.x - 100, bitmapSize.y))) {
+                for (int mode: ALL_MODES) {
+                    for (boolean rtl: List.of(true, false)) {
+                        for (boolean parallax: List.of(true, false)) {
+                            assertThat(WallpaperCropper.getAdjustedCrop(
+                                    crop, bitmapSize, displaySize, parallax, rtl, mode))
+                                    .isEqualTo(crop);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with parallax = true,
+     * does not keep more width than needed for {@link WallpaperCropper#MAX_PARALLAX}.
+     */
+    @Test
+    public void testGetAdjustedCrop_tooMuchParallax() {
+        Point displaySize = new Point(1000, 1000);
+        int tooLargeWidth = (int) (displaySize.x * (1 + 2 * WallpaperCropper.MAX_PARALLAX));
+        Point bitmapSize = new Point(tooLargeWidth, 1000);
+        Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+        int expectedWidth = (int) (displaySize.x * (1 + WallpaperCropper.MAX_PARALLAX));
+        Point expectedCropSize = new Point(expectedWidth, 1000);
+        for (int mode: ALL_MODES) {
+            assertThat(WallpaperCropper.getAdjustedCrop(
+                    crop, bitmapSize, displaySize, true, false, mode))
+                    .isEqualTo(leftOf(crop, expectedCropSize));
+            assertThat(WallpaperCropper.getAdjustedCrop(
+                    crop, bitmapSize, displaySize, true, true, mode))
+                    .isEqualTo(rightOf(crop, expectedCropSize));
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with parallax = true,
+     * does not remove parallax if the parallax is below {@link WallpaperCropper#MAX_PARALLAX}.
+     */
+    @Test
+    public void testGetAdjustedCrop_acceptableParallax() {
+        Point displaySize = new Point(1000, 1000);
+        List<Integer> acceptableWidths = List.of(displaySize.x,
+                (int) (displaySize.x * (1 + 0.5 * WallpaperCropper.MAX_PARALLAX)),
+                (int) (displaySize.x * (1 + 0.9 * WallpaperCropper.MAX_PARALLAX)),
+                (int) (displaySize.x * (1 + 1.0 * WallpaperCropper.MAX_PARALLAX)));
+        for (int acceptableWidth: acceptableWidths) {
+            Point bitmapSize = new Point(acceptableWidth, 1000);
+            Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+            for (int mode : ALL_MODES) {
+                for (boolean rtl : List.of(false, true)) {
+                    assertThat(WallpaperCropper.getAdjustedCrop(
+                            crop, bitmapSize, displaySize, true, rtl, mode))
+                            .isEqualTo(crop);
+                }
+            }
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+     * {@link WallpaperCropper#ADD}, correctly enlarges the crop to match the display dimensions,
+     * and adds content to the crop by an equal amount on both sides when possible.
+     */
+    @Test
+    public void testGetAdjustedCrop_add() {
+        Point displaySize = new Point(1000, 1000);
+        Point bitmapSize = new Point(1000, 1000);
+
+        List<Rect> crops = List.of(
+                new Rect(0, 0, 900, 1000),
+                new Rect(0, 0, 1000, 900),
+                new Rect(0, 0, 400, 500),
+                new Rect(500, 600, 1000, 1000));
+
+        List<Rect> expectedAdjustedCrops = List.of(
+                new Rect(0, 0, 1000, 1000),
+                new Rect(0, 0, 1000, 1000),
+                new Rect(0, 0, 500, 500),
+                new Rect(500, 500, 1000, 1000));
+
+        for (int i = 0; i < crops.size(); i++) {
+            Rect crop = crops.get(i);
+            Rect expectedCrop = expectedAdjustedCrops.get(i);
+            for (boolean rtl: List.of(false, true)) {
+                assertThat(WallpaperCropper.getAdjustedCrop(
+                        crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.ADD))
+                        .isEqualTo(expectedCrop);
+            }
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+     * {@link WallpaperCropper#REMOVE}, correctly shrinks the crop to match the display dimensions,
+     * and removes content by an equal amount on both sides.
+     */
+    @Test
+    public void testGetAdjustedCrop_remove() {
+        Point displaySize = new Point(1000, 1000);
+        Point bitmapSize = new Point(1500, 1500);
+
+        List<Rect> crops = List.of(
+                new Rect(50, 0, 1150, 1000),
+                new Rect(0, 50, 1000, 1150));
+
+        Point expectedCropSize = new Point(1000, 1000);
+
+        for (Rect crop: crops) {
+            for (boolean rtl : List.of(false, true)) {
+                assertThat(WallpaperCropper.getAdjustedCrop(
+                        crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.REMOVE))
+                        .isEqualTo(centerOf(crop, expectedCropSize));
+            }
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+     * {@link WallpaperCropper#BALANCE}, gives an adjusted crop with the same center and same number
+     * of pixels when possible.
+     */
+    @Test
+    public void testGetAdjustedCrop_balance() {
+        Point displaySize = new Point(500, 1000);
+        Point transposedDisplaySize = new Point(1000, 500);
+        Point bitmapSize = new Point(1000, 1000);
+
+        List<Rect> crops = List.of(
+                new Rect(0, 250, 1000, 750),
+                new Rect(100, 0, 300, 100));
+
+        List<Rect> expectedAdjustedCrops = List.of(
+                new Rect(250, 0, 750, 1000),
+                new Rect(150, 0, 250, 200));
+
+        for (int i = 0; i < crops.size(); i++) {
+            Rect crop = crops.get(i);
+            Rect expected = expectedAdjustedCrops.get(i);
+            assertThat(WallpaperCropper.getAdjustedCrop(
+                    crop, bitmapSize, displaySize, false, false, WallpaperCropper.BALANCE))
+                    .isEqualTo(expected);
+
+            Rect transposedCrop = new Rect(crop.top, crop.left, crop.bottom, crop.right);
+            Rect expectedTransposed = new Rect(
+                    expected.top, expected.left, expected.bottom, expected.right);
+            assertThat(WallpaperCropper.getAdjustedCrop(transposedCrop, bitmapSize,
+                    transposedDisplaySize, false, false, WallpaperCropper.BALANCE))
+                    .isEqualTo(expectedTransposed);
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getCrop} follows a simple centre-align strategy when
+     * no suggested crops are provided.
+     */
+    @Test
+    public void testGetCrop_noSuggestedCrops_centersWallpaper() {
+        setUpWithDisplays(STANDARD_DISPLAY);
+        Point bitmapSize = new Point(800, 1000);
+        Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+        SparseArray<Rect> suggestedCrops = new SparseArray<>();
+
+        List<Point> displaySizes = List.of(
+                new Point(500, 1000),
+                new Point(1000, 500));
+        List<Point> expectedCropSizes = List.of(
+                new Point(500, 1000),
+                new Point(800, 400));
+
+        for (int i = 0; i < displaySizes.size(); i++) {
+            Point displaySize = displaySizes.get(i);
+            Point expectedCropSize = expectedCropSizes.get(i);
+            for (boolean rtl : List.of(false, true)) {
+                assertThat(mWallpaperCropper.getCrop(
+                        displaySize, bitmapSize, suggestedCrops, rtl))
+                        .isEqualTo(centerOf(bitmapRect, expectedCropSize));
+            }
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getCrop} reuses a suggested crop of the same orientation
+     * as the display if possible, and does not remove additional width for parallax,
+     * but adds width if necessary.
+     */
+    @Test
+    public void testGetCrop_hasSuggestedCrop() {
+        setUpWithDisplays(STANDARD_DISPLAY);
+        Point bitmapSize = new Point(800, 1000);
+        SparseArray<Rect> suggestedCrops = new SparseArray<>();
+        suggestedCrops.put(PORTRAIT, new Rect(0, 0, 400, 800));
+        for (int otherOrientation: List.of(LANDSCAPE, SQUARE_LANDSCAPE, SQUARE_PORTRAIT)) {
+            suggestedCrops.put(otherOrientation, new Rect(0, 0, 10, 10));
+        }
+
+        for (boolean rtl : List.of(false, true)) {
+            assertThat(mWallpaperCropper.getCrop(
+                    new Point(300, 800), bitmapSize, suggestedCrops, rtl))
+                    .isEqualTo(suggestedCrops.get(PORTRAIT));
+            assertThat(mWallpaperCropper.getCrop(
+                    new Point(500, 800), bitmapSize, suggestedCrops, rtl))
+                    .isEqualTo(new Rect(0, 0, 500, 800));
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getCrop}, if there is no suggested crop of the same
+     * orientation as the display, reuses a suggested crop of the rotated orientation if possible,
+     * and preserves the center and number of pixels of the crop if possible.
+     * <p>
+     * To simplify, in this test case all crops have the same size as the display (no zoom)
+     * and are at the center of the image. Also the image is large enough to preserver the number
+     * of pixels (no additional zoom required).
+     */
+    @Test
+    public void testGetCrop_hasRotatedSuggestedCrop() {
+        setUpWithDisplays(STANDARD_DISPLAY);
+        Point bitmapSize = new Point(2000, 1800);
+        Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+        SparseArray<Rect> suggestedCrops = new SparseArray<>();
+        Point portrait = PORTRAIT_ONE;
+        Point landscape = new Point(PORTRAIT_ONE.y, PORTRAIT_ONE.x);
+        Point squarePortrait = SQUARE_PORTRAIT_ONE;
+        Point squareLandscape = new Point(SQUARE_PORTRAIT_ONE.y, SQUARE_PORTRAIT_ONE.y);
+        suggestedCrops.put(PORTRAIT, centerOf(bitmapRect, portrait));
+        suggestedCrops.put(SQUARE_LANDSCAPE, centerOf(bitmapRect, squareLandscape));
+        for (boolean rtl : List.of(false, true)) {
+            assertThat(mWallpaperCropper.getCrop(
+                    landscape, bitmapSize, suggestedCrops, rtl))
+                    .isEqualTo(centerOf(bitmapRect, landscape));
+
+            assertThat(mWallpaperCropper.getCrop(
+                    squarePortrait, bitmapSize, suggestedCrops, rtl))
+                    .isEqualTo(centerOf(bitmapRect, squarePortrait));
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getCrop}, when asked for a folded crop with a suggested
+     * crop only for the relative unfolded orientation, creates the folded crop at the center of the
+     * unfolded crop, by removing content on two sides to match the folded screen dimensions.
+     * <p>
+     * To simplify, in this test case all crops have the same size as the display (no zoom)
+     * and are at the center of the image.
+     */
+    @Test
+    public void testGetCrop_hasUnfoldedSuggestedCrop() {
+        for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+            setUpWithDisplays(displaySizes);
+            Point bitmapSize = new Point(2000, 2400);
+            Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+
+            Point largestDisplay = displaySizes.stream().max(
+                    Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+            int unfoldedOne = getOrientation(largestDisplay);
+            int unfoldedTwo = getRotatedOrientation(unfoldedOne);
+            Rect unfoldedCropOne = centerOf(bitmapRect, mDisplaySizes.get(unfoldedOne));
+            Rect unfoldedCropTwo = centerOf(bitmapRect, mDisplaySizes.get(unfoldedTwo));
+            SparseArray<Rect> suggestedCrops = new SparseArray<>();
+            suggestedCrops.put(unfoldedOne, unfoldedCropOne);
+            suggestedCrops.put(unfoldedTwo, unfoldedCropTwo);
+
+            int foldedOne = getFoldedOrientation(unfoldedOne);
+            int foldedTwo = getFoldedOrientation(unfoldedTwo);
+            Point foldedDisplayOne = mDisplaySizes.get(foldedOne);
+            Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo);
+
+            for (boolean rtl : List.of(false, true)) {
+                assertThat(mWallpaperCropper.getCrop(
+                        foldedDisplayOne, bitmapSize, suggestedCrops, rtl))
+                        .isEqualTo(centerOf(unfoldedCropOne, foldedDisplayOne));
+
+                assertThat(mWallpaperCropper.getCrop(
+                        foldedDisplayTwo, bitmapSize, suggestedCrops, rtl))
+                        .isEqualTo(centerOf(unfoldedCropTwo, foldedDisplayTwo));
+            }
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getCrop}, when asked for an unfolded crop with a suggested
+     * crop only for the relative folded orientation, creates the unfolded crop with the same center
+     * as the folded crop, by adding content on two sides to match the unfolded screen dimensions.
+     * <p>
+     * To simplify, in this test case all crops have the same size as the display (no zoom) and are
+     * at the center of the image. Also the image is large enough to add content.
+     */
+    @Test
+    public void testGetCrop_hasFoldedSuggestedCrop() {
+        for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+            setUpWithDisplays(displaySizes);
+            Point bitmapSize = new Point(2000, 2000);
+            Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+
+            Point smallestDisplay = displaySizes.stream().min(
+                    Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+            int foldedOne = getOrientation(smallestDisplay);
+            int foldedTwo = getRotatedOrientation(foldedOne);
+            Point foldedDisplayOne = mDisplaySizes.get(foldedOne);
+            Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo);
+            Rect foldedCropOne = centerOf(bitmapRect, foldedDisplayOne);
+            Rect foldedCropTwo = centerOf(bitmapRect, foldedDisplayTwo);
+            SparseArray<Rect> suggestedCrops = new SparseArray<>();
+            suggestedCrops.put(foldedOne, foldedCropOne);
+            suggestedCrops.put(foldedTwo, foldedCropTwo);
+
+            int unfoldedOne = getUnfoldedOrientation(foldedOne);
+            int unfoldedTwo = getUnfoldedOrientation(foldedTwo);
+            Point unfoldedDisplayOne = mDisplaySizes.get(unfoldedOne);
+            Point unfoldedDisplayTwo = mDisplaySizes.get(unfoldedTwo);
+
+            for (boolean rtl : List.of(false, true)) {
+                assertThat(centerOf(mWallpaperCropper.getCrop(
+                        unfoldedDisplayOne, bitmapSize, suggestedCrops, rtl), foldedDisplayOne))
+                        .isEqualTo(foldedCropOne);
+
+                assertThat(centerOf(mWallpaperCropper.getCrop(
+                        unfoldedDisplayTwo, bitmapSize, suggestedCrops, rtl), foldedDisplayTwo))
+                        .isEqualTo(foldedCropTwo);
+            }
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getCrop}, when asked for an folded crop with a suggested
+     * crop only for the rotated unfolded orientation, creates the folded crop from that crop by
+     * combining a rotate + fold operation. The folded crop should have less pixels than the
+     * unfolded crop due to the fold operation which removes content on both sides of the image.
+     * <p>
+     * To simplify, in this test case all crops have the same size as the display (no zoom)
+     * and are at the center of the image.
+     */
+    @Test
+    public void testGetCrop_hasRotatedUnfoldedSuggestedCrop() {
+        for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+            setUpWithDisplays(displaySizes);
+            Point bitmapSize = new Point(2000, 2000);
+            Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+            Point largestDisplay = displaySizes.stream().max(
+                    Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+            int unfoldedOne = getOrientation(largestDisplay);
+            int unfoldedTwo = getRotatedOrientation(unfoldedOne);
+            for (int unfolded: List.of(unfoldedOne, unfoldedTwo)) {
+                Rect unfoldedCrop = centerOf(bitmapRect, mDisplaySizes.get(unfolded));
+                int rotatedUnfolded = getRotatedOrientation(unfolded);
+                Rect rotatedUnfoldedCrop = centerOf(bitmapRect, mDisplaySizes.get(rotatedUnfolded));
+                SparseArray<Rect> suggestedCrops = new SparseArray<>();
+                suggestedCrops.put(unfolded, unfoldedCrop);
+                int rotatedFolded = getFoldedOrientation(rotatedUnfolded);
+                Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded);
+
+                for (boolean rtl : List.of(false, true)) {
+                    assertThat(mWallpaperCropper.getCrop(
+                            rotatedFoldedDisplay, bitmapSize, suggestedCrops, rtl))
+                            .isEqualTo(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay));
+                }
+            }
+        }
+    }
+
+    /**
+     * Test that {@link WallpaperCropper#getCrop}, when asked for an unfolded crop with a suggested
+     * crop only for the rotated folded orientation, creates the unfolded crop from that crop by
+     * combining a rotate + unfold operation. The unfolded crop should have more pixels than the
+     * folded crop due to the unfold operation which adds content on two sides of the image.
+     * <p>
+     * To simplify, in this test case all crops have the same size as the display (no zoom)
+     * and are centered inside the image. Also the image is large enough to add content.
+     */
+    @Test
+    public void testGetCrop_hasRotatedFoldedSuggestedCrop() {
+        for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+            setUpWithDisplays(displaySizes);
+            Point bitmapSize = new Point(2000, 2000);
+            Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+
+            Point smallestDisplay = displaySizes.stream().min(
+                    Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+            int foldedOne = getOrientation(smallestDisplay);
+            int foldedTwo = getRotatedOrientation(foldedOne);
+            for (int folded: List.of(foldedOne, foldedTwo)) {
+                Rect foldedCrop = centerOf(bitmapRect, mDisplaySizes.get(folded));
+                SparseArray<Rect> suggestedCrops = new SparseArray<>();
+                suggestedCrops.put(folded, foldedCrop);
+                int rotatedFolded = getRotatedOrientation(folded);
+                int rotatedUnfolded = getUnfoldedOrientation(rotatedFolded);
+                Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded);
+                Rect rotatedFoldedCrop = centerOf(bitmapRect, rotatedFoldedDisplay);
+                Point rotatedUnfoldedDisplay = mDisplaySizes.get(rotatedUnfolded);
+
+                for (boolean rtl : List.of(false, true)) {
+                    Rect rotatedUnfoldedCrop = mWallpaperCropper.getCrop(
+                            rotatedUnfoldedDisplay, bitmapSize, suggestedCrops, rtl);
+                    assertThat(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay))
+                            .isEqualTo(rotatedFoldedCrop);
+                }
+            }
+        }
+    }
+
+    private static Rect centerOf(Rect container, Point point) {
+        checkSubset(container, point);
+        int diffWidth = container.width() - point.x;
+        int diffHeight = container.height() - point.y;
+        int startX = container.left + diffWidth / 2;
+        int startY = container.top + diffHeight / 2;
+        return new Rect(startX, startY, startX + point.x, startY + point.y);
+    }
+
+    private static Rect leftOf(Rect container, Point point) {
+        Rect result = centerOf(container, point);
+        result.offset(container.left - result.left, 0);
+        return result;
+    }
+
+    private static Rect rightOf(Rect container, Point point) {
+        checkSubset(container, point);
+        Rect result = centerOf(container, point);
+        result.offset(container.right - result.right, 0);
+        return result;
+    }
+
+    private static void checkSubset(Rect container, Point point) {
+        if (container.width() < point.x || container.height() < point.y) {
+            throw new IllegalArgumentException();
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
index bb00634..fa1fd90 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -344,6 +344,21 @@
         verifyNoMoreInteractions(mSensorManager);
     }
 
+    @Test
+    public void testAwaitLuxWhenNoLightSensor() {
+        when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(null);
+        mProbe = new ALSProbe(mSensorManager, new Handler(mLooper.getLooper()), TIMEOUT_MS - 1);
+
+        AtomicInteger lux = new AtomicInteger(-5);
+        mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+
+        // Verify that no light sensor will be registered.
+        verify(mSensorManager, times(0)).registerListener(
+                mSensorEventListenerCaptor.capture(), any(), anyInt());
+
+        assertThat(lux.get()).isEqualTo(-1);
+    }
+
     private void moveTimeBy(long millis) {
         mLooper.moveTimeForward(millis);
         mLooper.processAllMessages();
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index ea84eb2..e0ef035 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.os;
 
-import android.app.admin.flags.Flags;
-
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -27,15 +25,19 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.when;
 
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.flags.Flags;
 import android.app.role.RoleManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Binder;
 import android.os.BugreportManager.BugreportCallback;
+import android.os.BugreportParams;
 import android.os.IBinder;
 import android.os.IDumpstateListener;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.UserManager;
 import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
@@ -75,6 +77,10 @@
 
     @Mock
     private PackageManager mPackageManager;
+    @Mock
+    private UserManager mMockUserManager;
+    @Mock
+    private DevicePolicyManager mMockDevicePolicyManager;
 
     private int mCallingUid = 1234;
     private String mCallingPackage  = "test.package";
@@ -91,10 +97,12 @@
         ArraySet<String> mAllowlistedPackages = new ArraySet<>();
         mAllowlistedPackages.add(mContext.getPackageName());
         mService = new BugreportManagerServiceImpl(
-                new BugreportManagerServiceImpl.Injector(mContext, mAllowlistedPackages,
-                        mMappingFile));
+                new TestInjector(mContext, mAllowlistedPackages, mMappingFile,
+                        mMockUserManager, mMockDevicePolicyManager));
         mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
         when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(mCallingUid);
+        // The calling user is an admin user by default.
+        when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(true);
     }
 
     @After
@@ -182,6 +190,36 @@
     }
 
     @Test
+    public void testStartBugreport_throwsForNonAdminUser() throws Exception {
+        when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false);
+
+        Exception thrown = assertThrows(IllegalArgumentException.class,
+                () -> mService.startBugreport(mCallingUid, mContext.getPackageName(),
+                        new FileDescriptor(), /* screenshotFd= */ null,
+                        BugreportParams.BUGREPORT_MODE_FULL,
+                        /* flags= */ 0, new Listener(new CountDownLatch(1)),
+                        /* isScreenshotRequested= */ false));
+
+        assertThat(thrown.getMessage()).contains("not an admin user");
+    }
+
+    @Test
+    public void testStartBugreport_throwsForNotAffiliatedUser() throws Exception {
+        when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false);
+        when(mMockDevicePolicyManager.getDeviceOwnerUserId()).thenReturn(-1);
+        when(mMockDevicePolicyManager.isAffiliatedUser(anyInt())).thenReturn(false);
+
+        Exception thrown = assertThrows(IllegalArgumentException.class,
+                () -> mService.startBugreport(mCallingUid, mContext.getPackageName(),
+                        new FileDescriptor(), /* screenshotFd= */ null,
+                        BugreportParams.BUGREPORT_MODE_REMOTE,
+                        /* flags= */ 0, new Listener(new CountDownLatch(1)),
+                        /* isScreenshotRequested= */ false));
+
+        assertThat(thrown.getMessage()).contains("not affiliated to the device owner");
+    }
+
+    @Test
     public void testRetrieveBugreportWithoutFilesForCaller() throws Exception {
         CountDownLatch latch = new CountDownLatch(1);
         Listener listener = new Listener(latch);
@@ -224,7 +262,8 @@
 
     private void clearAllowlist() {
         mService = new BugreportManagerServiceImpl(
-                new BugreportManagerServiceImpl.Injector(mContext, new ArraySet<>(), mMappingFile));
+                new TestInjector(mContext, new ArraySet<>(), mMappingFile,
+                        mMockUserManager, mMockDevicePolicyManager));
     }
 
     private static class Listener implements IDumpstateListener {
@@ -275,4 +314,27 @@
             complete(successful);
         }
     }
+
+    private static class TestInjector extends BugreportManagerServiceImpl.Injector {
+
+        private final UserManager mUserManager;
+        private final DevicePolicyManager mDevicePolicyManager;
+
+        TestInjector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile,
+                UserManager um, DevicePolicyManager dpm) {
+            super(context, allowlistedPackages, mappingFile);
+            mUserManager = um;
+            mDevicePolicyManager = dpm;
+        }
+
+        @Override
+        public UserManager getUserManager() {
+            return mUserManager;
+        }
+
+        @Override
+        public DevicePolicyManager getDevicePolicyManager() {
+            return mDevicePolicyManager;
+        }
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 33ca5c2..a45b102 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -20,6 +20,7 @@
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 
+import static junit.framework.Assert.fail;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
@@ -32,6 +33,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -47,9 +49,12 @@
 import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Xml;
+import android.Manifest;
 
+import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.function.TriPredicate;
 import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.UiServiceTestCase;
 import com.android.server.notification.NotificationManagerService.NotificationAssistants;
 
@@ -59,7 +64,9 @@
 import org.mockito.MockitoAnnotations;
 
 import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -89,11 +96,15 @@
     UserInfo mZero = new UserInfo(0, "zero", 0);
     UserInfo mTen = new UserInfo(10, "ten", 0);
 
+    ComponentName mCn = new ComponentName("a", "b");
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mContext.setMockPackageManager(mPm);
         mContext.addMockSystemService(Context.USER_SERVICE, mUm);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.string.config_defaultAssistantAccessComponent, "a/a");
         mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
         when(mNm.getBinderService()).thenReturn(mINm);
         mContext.ensureTestableResources();
@@ -102,8 +113,9 @@
         ResolveInfo resolve = new ResolveInfo();
         approved.add(resolve);
         ServiceInfo info = new ServiceInfo();
-        info.packageName = "a";
-        info.name="a";
+        info.packageName = mCn.getPackageName();
+        info.name = mCn.getClassName();
+        info.permission = Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE;
         resolve.serviceInfo = info;
         when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
                 .thenReturn(approved);
@@ -137,6 +149,51 @@
     }
 
     @Test
+    public void testWriteXml_userTurnedOffNAS() throws Exception {
+        int userId = ActivityManager.getCurrentUser();
+
+        mAssistants.loadDefaultsFromConfig(true);
+
+        mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
+               true, true);
+
+        ComponentName current = CollectionUtils.firstOrNull(
+                mAssistants.getAllowedComponents(userId));
+        assertNotNull(current);
+        mAssistants.setUserSet(userId, true);
+        mAssistants.setPackageOrComponentEnabled(current.flattenToString(), userId, true, false,
+                true);
+
+        TypedXmlSerializer serializer = Xml.newFastSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+        serializer.startDocument(null, true);
+        mAssistants.writeXml(serializer, true, userId);
+        serializer.endDocument();
+        serializer.flush();
+
+        //fail(baos.toString("UTF-8"));
+
+        final TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray())), null);
+        TriPredicate<String, Integer, String> allowedManagedServicePackages =
+                mNm::canUseManagedServices;
+
+        parser.nextTag();
+        mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
+        mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+
+        ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0);
+        // approved should not be null
+        assertNotNull(approved);
+        assertEquals(new ArraySet<>(), approved.get(true));
+
+        // user set is maintained
+        assertTrue(mAssistants.mIsUserChanged.get(ActivityManager.getCurrentUser()));
+    }
+
+    @Test
     public void testReadXml_userDisabled() throws Exception {
         String xml = "<enabled_assistants version=\"4\" defaults=\"b/b\">"
                 + "<service_listing approved=\"\" user=\"0\" primary=\"true\""
@@ -160,6 +217,33 @@
     }
 
     @Test
+    public void testReadXml_userDisabled_restore() throws Exception {
+        String xml = "<enabled_assistants version=\"4\" defaults=\"b/b\">"
+                + "<service_listing approved=\"\" user=\"0\" primary=\"true\""
+                + "user_changed=\"true\"/>"
+                + "</enabled_assistants>";
+
+        final TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(xml.toString().getBytes())), null);
+        TriPredicate<String, Integer, String> allowedManagedServicePackages =
+                mNm::canUseManagedServices;
+
+        parser.nextTag();
+        mAssistants.readXml(parser, allowedManagedServicePackages, true,
+                ActivityManager.getCurrentUser());
+
+        ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0);
+
+        // approved should not be null
+        assertNotNull(approved);
+        assertEquals(new ArraySet<>(), approved.get(true));
+
+        // user set is maintained
+        assertTrue(mAssistants.mIsUserChanged.get(ActivityManager.getCurrentUser()));
+    }
+
+    @Test
     public void testReadXml_upgradeUserSet() throws Exception {
         String xml = "<enabled_assistants version=\"3\" defaults=\"b/b\">"
                 + "<service_listing approved=\"\" user=\"0\" primary=\"true\""
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
index 3499a12..bf8cfa5c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
@@ -20,8 +20,12 @@
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.TestCase.assertFalse;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -70,10 +74,11 @@
 
     @Before
     public void setUp() throws Exception {
-        mJobService = new NotificationHistoryJobService();
+        mJobService = spy(new NotificationHistoryJobService());
         mJobService.attachBaseContext(mContext);
         mJobService.onCreate();
         mJobService.onBind(/* intent= */ null);  // Create JobServiceEngine within JobService.
+        doNothing().when(mJobService).jobFinished(any(), eq(false));
 
         mContext.addMockSystemService(JobScheduler.class, mMockJobScheduler);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
index 99d5a6d..75552bc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server.notification;
 
-import static com.android.server.notification.ZenAdapters.notificationPolicyToZenPolicy;
+import static android.service.notification.ZenAdapters.notificationPolicyToZenPolicy;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 495e01a..7c1adbc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -136,6 +136,7 @@
 import android.provider.Settings.Global;
 import android.service.notification.Condition;
 import android.service.notification.DeviceEffectsApplier;
+import android.service.notification.ZenAdapters;
 import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 6013063..da11e6a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -554,7 +554,7 @@
         mRootWindowContainer.applySleepTokens(true);
 
         // The display orientation should be changed by the activity so there is no relaunch.
-        verify(activity, never()).relaunchActivityLocked(anyBoolean());
+        verify(activity, never()).relaunchActivityLocked(anyBoolean(), anyInt());
         assertEquals(rotatedConfig.orientation, display.getConfiguration().orientation);
     }
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 1e1dd00..5a52968 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -66,7 +66,6 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
-import android.os.PermissionEnforcer;
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
@@ -76,7 +75,6 @@
 import android.os.ShellCallback;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.permission.flags.Flags;
 import android.provider.Settings;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
 import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
@@ -1407,17 +1405,6 @@
             }
         }
 
-        // Enforce permissions that are flag controlled. The flag value decides if the permission
-        // should be enforced.
-        private void initAndVerifyDetector_enforcePermissionWithFlags() {
-            PermissionEnforcer enforcer = mContext.getSystemService(PermissionEnforcer.class);
-            if (Flags.voiceActivationPermissionApis()) {
-                enforcer.enforcePermission(
-                        android.Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO,
-                        getCallingPid(), getCallingUid());
-            }
-        }
-
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION)
         @Override
         public void initAndVerifyDetector(
@@ -1427,13 +1414,7 @@
                 @NonNull IBinder token,
                 IHotwordRecognitionStatusCallback callback,
                 int detectorType) {
-            // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
-            // {@link #initAndVerifyDetector(Identity,  PersistableBundle, ShareMemory, IBinder,
-            // IHotwordRecognitionStatusCallback, int)}
-            // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully
-            // launched.
             super.initAndVerifyDetector_enforcePermission();
-            initAndVerifyDetector_enforcePermissionWithFlags();
 
             synchronized (this) {
                 enforceIsCurrentVoiceInteractionService();
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 9792cdd..048b1b2 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1434,11 +1434,14 @@
     }
 
     /**
-     * This API will return all {@link PhoneAccount}s registered via
-     * {@link TelecomManager#registerPhoneAccount(PhoneAccount)}. If a {@link PhoneAccount} appears
-     * to be missing from the list, Telecom has either unregistered the {@link PhoneAccount}
-     * or the caller registered the {@link PhoneAccount} under a different user and does not
-     * have the {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
+     * This API will return all {@link PhoneAccount}s the caller registered via
+     * {@link TelecomManager#registerPhoneAccount(PhoneAccount)}.  If a {@link PhoneAccount} appears
+     * to be missing from the list, Telecom has either unregistered the {@link PhoneAccount} (for
+     * cleanup purposes) or the caller registered the {@link PhoneAccount} under a different user
+     * and does not have the  {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
+     * <b>Note:</b> This API will only return {@link PhoneAccount}s registered by the same app.  For
+     * system Dialers that need all the {@link PhoneAccount}s registered by every application, see
+     * {@link TelecomManager#getAllPhoneAccounts()}.
      *
      * @return all the {@link PhoneAccount}s registered by the caller.
      */
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index 7343ba1c..e60764f 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -24,6 +24,7 @@
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
 import android.content.pm.ServiceInfo
+import android.hardware.input.KeyboardLayoutSelectionResult
 import android.hardware.input.IInputManager
 import android.hardware.input.InputManager
 import android.hardware.input.InputManagerGlobal
@@ -525,13 +526,13 @@
                 keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
                 ENGLISH_UK_LAYOUT_DESCRIPTOR
             )
-            val keyboardLayout =
+            assertEquals(
+                "Default UI: getKeyboardLayoutForInputDevice API should always return " +
+                        "KeyboardLayoutSelectionResult.FAILED",
+                KeyboardLayoutSelectionResult.FAILED,
                 keyboardLayoutManager.getKeyboardLayoutForInputDevice(
                     keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
                 )
-            assertNull(
-                "Default UI: getKeyboardLayoutForInputDevice API should always return null",
-                keyboardLayout
             )
         }
     }
@@ -545,12 +546,14 @@
                 keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
                 ENGLISH_UK_LAYOUT_DESCRIPTOR
             )
-            assertEquals(
-                "New UI: getKeyboardLayoutForInputDevice API should return the set layout",
-                ENGLISH_UK_LAYOUT_DESCRIPTOR,
+            var result =
                 keyboardLayoutManager.getKeyboardLayoutForInputDevice(
                     keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
                 )
+            assertEquals(
+                "New UI: getKeyboardLayoutForInputDevice API should return the set layout",
+                ENGLISH_UK_LAYOUT_DESCRIPTOR,
+                result.layoutDescriptor
             )
 
             // This should replace previously set layout
@@ -558,12 +561,14 @@
                 keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
                 ENGLISH_US_LAYOUT_DESCRIPTOR
             )
-            assertEquals(
-                "New UI: getKeyboardLayoutForInputDevice API should return the last set layout",
-                ENGLISH_US_LAYOUT_DESCRIPTOR,
+            result =
                 keyboardLayoutManager.getKeyboardLayoutForInputDevice(
                     keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
                 )
+            assertEquals(
+                "New UI: getKeyboardLayoutForInputDevice API should return the last set layout",
+                ENGLISH_US_LAYOUT_DESCRIPTOR,
+                result.layoutDescriptor
             )
         }
     }
@@ -734,17 +739,20 @@
                 createImeSubtypeForLanguageTag("ru"),
                 createLayoutDescriptor("keyboard_layout_russian")
             )
-            assertNull(
-                "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " +
-                        "layout available",
+            assertEquals(
+                "New UI: getDefaultKeyboardLayoutForInputDevice should return " +
+                        "KeyboardLayoutSelectionResult.FAILED when no layout available",
+                KeyboardLayoutSelectionResult.FAILED,
                 keyboardLayoutManager.getKeyboardLayoutForInputDevice(
                     keyboardDevice.identifier, USER_ID, imeInfo,
                     createImeSubtypeForLanguageTag("it")
                 )
             )
-            assertNull(
-                "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " +
-                        "layout for script code is available",
+            assertEquals(
+                "New UI: getDefaultKeyboardLayoutForInputDevice should return " +
+                        "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
+                        "available",
+                KeyboardLayoutSelectionResult.FAILED,
                 keyboardLayoutManager.getKeyboardLayoutForInputDevice(
                     keyboardDevice.identifier, USER_ID, imeInfo,
                     createImeSubtypeForLanguageTag("en-Deva")
@@ -811,8 +819,10 @@
                 createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
                 createLayoutDescriptor("keyboard_layout_russian")
             )
-            assertNull("New UI: getDefaultKeyboardLayoutForInputDevice should return null when " +
-                    "no layout for script code is available",
+            assertEquals("New UI: getDefaultKeyboardLayoutForInputDevice should return " +
+                    "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
+                    "available",
+                KeyboardLayoutSelectionResult.FAILED,
                 keyboardLayoutManager.getKeyboardLayoutForInputDevice(
                     keyboardDevice.identifier, USER_ID, imeInfo,
                     createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "")
@@ -865,14 +875,16 @@
                         ArgumentMatchers.anyBoolean(),
                         ArgumentMatchers.eq(keyboardDevice.vendorId),
                         ArgumentMatchers.eq(keyboardDevice.productId),
-                        ArgumentMatchers.eq(createByteArray(
+                        ArgumentMatchers.eq(
+                            createByteArray(
                                 KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
                                 LAYOUT_TYPE_DEFAULT,
                                 GERMAN_LAYOUT_NAME,
-                                KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+                                KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
                                 "de-Latn",
-                                LAYOUT_TYPE_QWERTZ),
+                                LAYOUT_TYPE_QWERTZ
                             ),
+                        ),
                         ArgumentMatchers.eq(keyboardDevice.deviceBus),
                 )
             }
@@ -893,13 +905,16 @@
                         ArgumentMatchers.anyBoolean(),
                         ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId),
                         ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId),
-                        ArgumentMatchers.eq(createByteArray(
+                        ArgumentMatchers.eq(
+                            createByteArray(
                                 "en",
                                 LAYOUT_TYPE_QWERTY,
                                 ENGLISH_US_LAYOUT_NAME,
-                                KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+                                KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
                                 "de-Latn",
-                                LAYOUT_TYPE_QWERTZ)),
+                                LAYOUT_TYPE_QWERTZ
+                            )
+                        ),
                         ArgumentMatchers.eq(keyboardDevice.deviceBus),
                 )
             }
@@ -918,14 +933,16 @@
                         ArgumentMatchers.anyBoolean(),
                         ArgumentMatchers.eq(keyboardDevice.vendorId),
                         ArgumentMatchers.eq(keyboardDevice.productId),
-                        ArgumentMatchers.eq(createByteArray(
+                        ArgumentMatchers.eq(
+                            createByteArray(
                                 KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
                                 LAYOUT_TYPE_DEFAULT,
                                 "Default",
-                                KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT,
+                                KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT,
                                 KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
-                                LAYOUT_TYPE_DEFAULT),
+                                LAYOUT_TYPE_DEFAULT
                             ),
+                        ),
                         ArgumentMatchers.eq(keyboardDevice.deviceBus),
                 )
             }
@@ -998,12 +1015,13 @@
         imeSubtype: InputMethodSubtype,
         expectedLayout: String
     ) {
+        val result = keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+            device.identifier, USER_ID, imeInfo, imeSubtype
+        )
         assertEquals(
             "New UI: getDefaultKeyboardLayoutForInputDevice should return $expectedLayout",
             expectedLayout,
-            keyboardLayoutManager.getKeyboardLayoutForInputDevice(
-                device.identifier, USER_ID, imeInfo, imeSubtype
-            )
+            result.layoutDescriptor
         )
     }
 
diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
index 89a47b9..0615941 100644
--- a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
@@ -17,6 +17,7 @@
 package com.android.server.input
 
 import android.hardware.input.KeyboardLayout
+import android.hardware.input.KeyboardLayoutSelectionResult
 import android.icu.util.ULocale
 import android.platform.test.annotations.Presubmit
 import android.view.InputDevice
@@ -120,15 +121,15 @@
         val event = builder.addLayoutSelection(
             createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"),
             "English(US)(Qwerty)",
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
+            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
         ).addLayoutSelection(
             createImeSubtype(2, ULocale.forLanguageTag("en-US"), "azerty"),
             null, // Default layout type
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER
+            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER
         ).addLayoutSelection(
             createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"),
             "German",
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE
         ).setIsFirstTimeConfiguration(true).build()
 
         assertEquals(
@@ -158,7 +159,7 @@
             "de-CH",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
             "English(US)(Qwerty)",
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
             "en-US",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
         )
@@ -167,7 +168,7 @@
             "de-CH",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
             KeyboardMetricsCollector.DEFAULT_LAYOUT_NAME,
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER,
+            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER,
             "en-US",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
         )
@@ -176,7 +177,7 @@
             "de-CH",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
             "German",
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
             "en-US",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
         )
@@ -197,7 +198,7 @@
         val event = builder.addLayoutSelection(
             createImeSubtype(4, null, "qwerty"), // Default language tag
             "German",
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE
         ).build()
 
         assertExpectedLayoutConfiguration(
@@ -205,7 +206,7 @@
             KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
             "German",
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+            KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
             KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
         )
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index b054a57..b4e2758 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -15,12 +15,7 @@
 //
 
 package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 toolSources = [
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
deleted file mode 100644
index 15ae2ba..0000000
--- a/tools/aapt2/Android.mk
+++ /dev/null
@@ -1,4 +0,0 @@
-include $(CLEAR_VARS)
-aapt2_results := ./out/soong/.intermediates/frameworks/base/tools/aapt2/aapt2_results
-$(call declare-1p-target,$(aapt2_results))
-aapt2_results :=