Merge "Move shade to the default display when the keyguard is visible" into main
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index a696e03..afd9984 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -6469,6 +6469,7 @@
 android.os.connectivity.WifiActivityEnergyInfo
 android.os.connectivity.WifiBatteryStats$1
 android.os.connectivity.WifiBatteryStats
+android.os.flagging.AconfigPackage
 android.os.health.HealthKeys$Constant
 android.os.health.HealthKeys$Constants
 android.os.health.HealthKeys$SortedIntArray
diff --git a/config/preloaded-classes b/config/preloaded-classes
index ed40276..343de0b 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -6473,6 +6473,7 @@
 android.os.connectivity.WifiActivityEnergyInfo
 android.os.connectivity.WifiBatteryStats$1
 android.os.connectivity.WifiBatteryStats
+android.os.flagging.AconfigPackage
 android.os.health.HealthKeys$Constant
 android.os.health.HealthKeys$Constants
 android.os.health.HealthKeys$SortedIntArray
diff --git a/core/api/current.txt b/core/api/current.txt
index 5efa747..a9febc3 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -29956,7 +29956,7 @@
   public class X509TrustManagerExtensions {
     ctor public X509TrustManagerExtensions(javax.net.ssl.X509TrustManager) throws java.lang.IllegalArgumentException;
     method public java.util.List<java.security.cert.X509Certificate> checkServerTrusted(java.security.cert.X509Certificate[], String, String) throws java.security.cert.CertificateException;
-    method @FlaggedApi("android.net.platform.flags.x509_extensions_certificate_transparency") @NonNull public java.util.List<java.security.cert.X509Certificate> checkServerTrusted(@NonNull java.security.cert.X509Certificate[], @Nullable byte[], @Nullable byte[], @NonNull String, @NonNull String) throws java.security.cert.CertificateException;
+    method @FlaggedApi("android.security.certificate_transparency_configuration") @NonNull public java.util.List<java.security.cert.X509Certificate> checkServerTrusted(@NonNull java.security.cert.X509Certificate[], @Nullable byte[], @Nullable byte[], @NonNull String, @NonNull String) throws java.security.cert.CertificateException;
     method public boolean isSameTrustConfiguration(String, String);
     method public boolean isUserAddedCertificate(java.security.cert.X509Certificate);
   }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 6230a59..8594cae 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1402,9 +1402,9 @@
     method @NonNull public android.credentials.selection.GetCredentialProviderData.Builder setRemoteEntry(@Nullable android.credentials.selection.Entry);
   }
 
-  @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public class IntentFactory {
-    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);
+  @FlaggedApi("android.credentials.flags.propagate_user_context_for_intent_creation") public class IntentFactory {
+    method @NonNull public static android.content.Intent createCancelUiIntent(@NonNull android.content.Context, @NonNull android.os.IBinder, boolean, @NonNull String, int);
+    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, int);
   }
 
   @FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public abstract class ProviderData implements android.os.Parcelable {
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index ed088fe..a731e50 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -34,6 +34,7 @@
 import android.os.OutcomeReceiver;
 import android.os.ParcelableException;
 import android.os.RemoteException;
+import android.os.SystemClock;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -179,7 +180,8 @@
 
         ExecuteAppFunctionAidlRequest aidlRequest =
                 new ExecuteAppFunctionAidlRequest(
-                        request, mContext.getUser(), mContext.getPackageName());
+                        request, mContext.getUser(), mContext.getPackageName(),
+                        /* requestTime= */ SystemClock.elapsedRealtime());
 
         try {
             ICancellationSignal cancellationTransport =
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionAidlRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionAidlRequest.java
index e623fa1..707d1fc 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionAidlRequest.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionAidlRequest.java
@@ -41,8 +41,9 @@
                             ExecuteAppFunctionRequest.CREATOR.createFromParcel(in);
                     UserHandle userHandle = UserHandle.CREATOR.createFromParcel(in);
                     String callingPackage = in.readString8();
+                    long requestTime = in.readLong();
                     return new ExecuteAppFunctionAidlRequest(
-                            clientRequest, userHandle, callingPackage);
+                            clientRequest, userHandle, callingPackage, requestTime);
                 }
 
                 @Override
@@ -60,11 +61,15 @@
     /** The package name of the app that is requesting to execute the app function. */
     private final String mCallingPackage;
 
-    public ExecuteAppFunctionAidlRequest(
-            ExecuteAppFunctionRequest clientRequest, UserHandle userHandle, String callingPackage) {
+    /** The time of calling executeAppFunction(). */
+    private final long mRequestTime;
+
+    public ExecuteAppFunctionAidlRequest(ExecuteAppFunctionRequest clientRequest,
+            UserHandle userHandle, String callingPackage, long requestTime) {
         this.mClientRequest = Objects.requireNonNull(clientRequest);
         this.mUserHandle = Objects.requireNonNull(userHandle);
         this.mCallingPackage = Objects.requireNonNull(callingPackage);
+        this.mRequestTime = requestTime;
     }
 
     @Override
@@ -77,6 +82,7 @@
         mClientRequest.writeToParcel(dest, flags);
         mUserHandle.writeToParcel(dest, flags);
         dest.writeString8(mCallingPackage);
+        dest.writeLong(mRequestTime);
     }
 
     /** Returns the client request to execute an app function. */
@@ -96,4 +102,9 @@
     public String getCallingPackage() {
         return mCallingPackage;
     }
+
+    /** Returns the time of calling executeAppFunction(). */
+    public long getRequestTime() {
+        return mRequestTime;
+    }
 }
diff --git a/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java
index 2426daf..e290169 100644
--- a/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java
+++ b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java
@@ -17,6 +17,7 @@
 package android.app.appfunctions;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -37,8 +38,16 @@
 
     @NonNull private final IExecuteAppFunctionCallback mCallback;
 
+    @Nullable CompletionCallback mCompletionCallback;
+
     public SafeOneTimeExecuteAppFunctionCallback(@NonNull IExecuteAppFunctionCallback callback) {
+        this(callback, /* completionCallback= */ null);
+    }
+
+    public SafeOneTimeExecuteAppFunctionCallback(@NonNull IExecuteAppFunctionCallback callback,
+            @Nullable CompletionCallback completionCallback) {
         mCallback = Objects.requireNonNull(callback);
+        mCompletionCallback = completionCallback;
     }
 
     /** Invoke wrapped callback with the result. */
@@ -49,6 +58,9 @@
         }
         try {
             mCallback.onSuccess(result);
+            if (mCompletionCallback != null) {
+                mCompletionCallback.finalizeOnSuccess(result);
+            }
         } catch (RemoteException ex) {
             // Failed to notify the other end. Ignore.
             Log.w(TAG, "Failed to invoke the callback", ex);
@@ -63,6 +75,9 @@
         }
         try {
             mCallback.onError(error);
+            if (mCompletionCallback != null) {
+                mCompletionCallback.finalizeOnError(error);
+            }
         } catch (RemoteException ex) {
             // Failed to notify the other end. Ignore.
             Log.w(TAG, "Failed to invoke the callback", ex);
@@ -76,4 +91,16 @@
     public void disable() {
         mOnResultCalled.set(true);
     }
+
+    /**
+     * Provides a hook to execute additional actions after the {@link IExecuteAppFunctionCallback}
+     * has been invoked.
+     */
+    public interface CompletionCallback {
+        /** Called after {@link IExecuteAppFunctionCallback#onSuccess}. */
+        void finalizeOnSuccess(@NonNull ExecuteAppFunctionResponse result);
+
+        /** Called after {@link IExecuteAppFunctionCallback#onError}. */
+        void finalizeOnError(@NonNull AppFunctionException error);
+    }
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index b3f09a9..ed2fd99 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -1290,7 +1290,11 @@
                 @NonNull UserHandle user) {}
 
         /**
-         * Called when a window with a secure surface is no longer shown on the device.
+         * Called when there is no longer any window with a secure surface shown on the device.
+         *
+         * <p>This is only called once there are no more secure windows shown on the device. If
+         * there are multiple secure windows shown on the device, this callback will be called only
+         * once all of them are hidden.</p>
          *
          * @param displayId The display ID on which the window was shown before.
          *
diff --git a/core/java/android/content/EventLogTags.logtags b/core/java/android/content/EventLogTags.logtags
index 21ea90a..861a5b7 100644
--- a/core/java/android/content/EventLogTags.logtags
+++ b/core/java/android/content/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
 
 option java_package android.content;
 
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index a6492d3..3d75423 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12291,7 +12291,6 @@
         private IBinder mCreatorToken;
         // Stores all extra keys whose values are intents for a top level intent.
         private ArraySet<NestedIntentKey> mNestedIntentKeys;
-
     }
 
     /**
@@ -12353,6 +12352,7 @@
         public int hashCode() {
             return Objects.hash(mType, mKey, mIndex);
         }
+
     }
 
     private @Nullable CreatorTokenInfo mCreatorTokenInfo;
@@ -12416,7 +12416,7 @@
                     // removeLaunchSecurityProtection() is called before it is launched.
                     value = null;
                 }
-                if (value instanceof Intent intent && !visited.contains(intent)) {
+                if (value instanceof Intent intent) {
                     handleNestedIntent(intent, visited, new NestedIntentKey(
                             NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL, key, 0));
                 } else if (value instanceof Parcelable[] parcelables) {
@@ -12439,7 +12439,6 @@
     }
 
     private void handleNestedIntent(Intent intent, Set<Intent> visited, NestedIntentKey key) {
-        visited.add(intent);
         if (mCreatorTokenInfo == null) {
             mCreatorTokenInfo = new CreatorTokenInfo();
         }
@@ -12447,7 +12446,10 @@
             mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>();
         }
         mCreatorTokenInfo.mNestedIntentKeys.add(key);
-        intent.collectNestedIntentKeysRecur(visited);
+        if (!visited.contains(intent)) {
+            visited.add(intent);
+            intent.collectNestedIntentKeysRecur(visited);
+        }
     }
 
     private void handleParcelableArray(Parcelable[] parcelables, String key, Set<Intent> visited) {
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 18a45d8d..5381301 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -539,9 +539,6 @@
                                     hasBindDeviceAdminPermission);
                             break;
                         case TAG_USES_SDK_LIBRARY:
-                            if (!android.content.pm.Flags.sdkDependencyInstaller()) {
-                                break;
-                            }
                             String usesSdkLibName = parser.getAttributeValue(
                                     ANDROID_RES_NAMESPACE, "name");
                             // TODO(b/379219371): Due to a bug in bundletool, some apps can use
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index 6fc7d90..ecb4bb1 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -114,3 +114,11 @@
     bug: "373535266"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "self_targeting_android_resource_frro"
+    is_exported: true
+    namespace: "customization_picker"
+    description: "Fixes bug in Launcher preview by enabling overlays targeting 'android'"
+    bug: "377545987"
+}
\ No newline at end of file
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
index 830b7e0..7eba181 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -25,6 +25,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.res.ApkAssets;
 import android.content.res.AssetFileDescriptor;
+import android.content.res.Flags;
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
 
@@ -90,6 +91,10 @@
             throws IOException {
         Objects.requireNonNull(overlayInfo);
         Preconditions.checkArgument(overlayInfo.isFabricated(), "Not accepted overlay");
+        if (!Flags.selfTargetingAndroidResourceFrro()) {
+            Preconditions.checkStringNotEmpty(
+                    overlayInfo.getTargetOverlayableName(), "Without overlayable name");
+        }
         final String overlayName =
                 OverlayManagerImpl.checkOverlayNameValid(overlayInfo.getOverlayName());
         final String path =
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index d8d4e16..9c811fb 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -124,3 +124,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    namespace: "credential_manager"
+    name: "settings_w_fixes"
+    description: "Settings improvements for credential manager"
+    bug: "373711451"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/credentials/selection/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java
index c521b96..59539c4 100644
--- a/core/java/android/credentials/selection/IntentFactory.java
+++ b/core/java/android/credentials/selection/IntentFactory.java
@@ -16,7 +16,7 @@
 
 package android.credentials.selection;
 
-import static android.credentials.flags.Flags.FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED;
+import static android.credentials.flags.Flags.FLAG_PROPAGATE_USER_CONTEXT_FOR_INTENT_CREATION;
 import static android.credentials.flags.Flags.configurableSelectorUiEnabled;
 
 import android.annotation.FlaggedApi;
@@ -24,6 +24,8 @@
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -32,6 +34,7 @@
 import android.content.res.Resources;
 import android.os.IBinder;
 import android.os.Parcel;
+import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.text.TextUtils;
 import android.util.Slog;
@@ -46,7 +49,7 @@
  * @hide
  */
 @TestApi
-@FlaggedApi(FLAG_CONFIGURABLE_SELECTOR_UI_ENABLED)
+@FlaggedApi(FLAG_PROPAGATE_USER_CONTEXT_FOR_INTENT_CREATION)
 public class IntentFactory {
 
     /**
@@ -65,9 +68,10 @@
             @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
             @NonNull
             ArrayList<DisabledProviderData> disabledProviderDataList,
-            @NonNull ResultReceiver resultReceiver) {
+            @NonNull ResultReceiver resultReceiver,
+            @UserIdInt int userId) {
         return createCredentialSelectorIntentInternal(context, requestInfo,
-                disabledProviderDataList, resultReceiver);
+                disabledProviderDataList, resultReceiver, userId);
     }
 
     /**
@@ -96,9 +100,10 @@
             @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
             @NonNull
             ArrayList<DisabledProviderData> disabledProviderDataList,
-            @NonNull ResultReceiver resultReceiver) {
+            @NonNull ResultReceiver resultReceiver,
+            @UserIdInt int userId) {
         IntentCreationResult result = createCredentialSelectorIntentInternal(context, requestInfo,
-                disabledProviderDataList, resultReceiver);
+                disabledProviderDataList, resultReceiver, userId);
         result.getIntent().putParcelableArrayListExtra(
                 ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
         return result;
@@ -130,9 +135,10 @@
             @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
             @NonNull
             ArrayList<DisabledProviderData> disabledProviderDataList,
-            @NonNull ResultReceiver resultReceiver) {
+            @NonNull ResultReceiver resultReceiver, @UserIdInt int userId) {
         return createCredentialSelectorIntentForCredMan(context, requestInfo,
-                enabledProviderDataList, disabledProviderDataList, resultReceiver).getIntent();
+            enabledProviderDataList, disabledProviderDataList, resultReceiver,
+            userId).getIntent();
     }
 
     /**
@@ -142,10 +148,10 @@
     @NonNull
     public static Intent createCancelUiIntent(@NonNull Context context,
             @NonNull IBinder requestToken, boolean shouldShowCancellationUi,
-            @NonNull String appPackageName) {
+            @NonNull String appPackageName, @UserIdInt int userId) {
         Intent intent = new Intent();
         IntentCreationResult.Builder intentResultBuilder = new IntentCreationResult.Builder(intent);
-        setCredentialSelectorUiComponentName(context, intent, intentResultBuilder);
+        setCredentialSelectorUiComponentName(context, intent, intentResultBuilder, userId);
         intent.putExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
                 new CancelSelectionRequest(new RequestToken(requestToken), shouldShowCancellationUi,
                         appPackageName));
@@ -162,10 +168,10 @@
             @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
             @NonNull
             ArrayList<DisabledProviderData> disabledProviderDataList,
-            @NonNull ResultReceiver resultReceiver) {
+            @NonNull ResultReceiver resultReceiver, @UserIdInt int userId) {
         Intent intent = new Intent();
         IntentCreationResult.Builder intentResultBuilder = new IntentCreationResult.Builder(intent);
-        setCredentialSelectorUiComponentName(context, intent, intentResultBuilder);
+        setCredentialSelectorUiComponentName(context, intent, intentResultBuilder, userId);
         intent.putParcelableArrayListExtra(
                 ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
         intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
@@ -175,9 +181,11 @@
     }
 
     private static void setCredentialSelectorUiComponentName(@NonNull Context context,
-            @NonNull Intent intent, @NonNull IntentCreationResult.Builder intentResultBuilder) {
+            @NonNull Intent intent, @NonNull IntentCreationResult.Builder intentResultBuilder,
+            @UserIdInt int userId) {
         if (configurableSelectorUiEnabled()) {
-            ComponentName componentName = getOemOverrideComponentName(context, intentResultBuilder);
+            ComponentName componentName = getOemOverrideComponentName(context,
+                    intentResultBuilder, userId);
 
             ComponentName fallbackUiComponentName = null;
             try {
@@ -210,7 +218,7 @@
      */
     @Nullable
     private static ComponentName getOemOverrideComponentName(@NonNull Context context,
-            @NonNull IntentCreationResult.Builder intentResultBuilder) {
+            @NonNull IntentCreationResult.Builder intentResultBuilder, @UserIdInt int userId) {
         ComponentName result = null;
         String oemComponentString =
                 Resources.getSystem()
@@ -228,35 +236,43 @@
             if (oemComponentName != null) {
                 try {
                     intentResultBuilder.setOemUiPackageName(oemComponentName.getPackageName());
-                    ActivityInfo info = context.getPackageManager().getActivityInfo(
-                            oemComponentName,
-                            PackageManager.ComponentInfoFlags.of(
-                                    PackageManager.MATCH_SYSTEM_ONLY));
-                    boolean oemComponentEnabled = info.enabled;
-                    int runtimeComponentEnabledState = context.getPackageManager()
+                    ActivityInfo info;
+                    if (android.credentials.flags.Flags.propagateUserContextForIntentCreation()) {
+                      info = context.getPackageManager().getActivityInfo(oemComponentName,
+                          PackageManager.ComponentInfoFlags.of(
+                              PackageManager.MATCH_SYSTEM_ONLY));
+                    } else {
+                      info = AppGlobals.getPackageManager().getActivityInfo(
+                          oemComponentName, 0, userId);
+                    }
+                    boolean oemComponentEnabled = false;
+                    if (info != null) {
+                      oemComponentEnabled = info.enabled;
+                      int runtimeComponentEnabledState = context.getPackageManager()
                           .getComponentEnabledSetting(oemComponentName);
-                    if (runtimeComponentEnabledState == PackageManager
+                      if (runtimeComponentEnabledState == PackageManager
                           .COMPONENT_ENABLED_STATE_ENABLED) {
-                          oemComponentEnabled = true;
-                    } else if (runtimeComponentEnabledState == PackageManager
+                        oemComponentEnabled = true;
+                      } else if (runtimeComponentEnabledState == PackageManager
                           .COMPONENT_ENABLED_STATE_DISABLED) {
                         oemComponentEnabled = false;
-                    }
-                    if (oemComponentEnabled && info.exported) {
+                      }
+                      if (oemComponentEnabled && info.exported) {
                         intentResultBuilder.setOemUiUsageStatus(IntentCreationResult
-                                .OemUiUsageStatus.SUCCESS);
+                            .OemUiUsageStatus.SUCCESS);
                         Slog.i(TAG,
-                                "Found enabled oem CredMan UI component."
-                                        + oemComponentString);
+                            "Found enabled oem CredMan UI component."
+                                + oemComponentString);
                         result = oemComponentName;
-                    } else {
-                        intentResultBuilder.setOemUiUsageStatus(IntentCreationResult
-                                .OemUiUsageStatus.OEM_UI_CONFIG_SPECIFIED_FOUND_BUT_NOT_ENABLED);
-                        Slog.i(TAG,
-                                "Found enabled oem CredMan UI component but it was not "
-                                        + "enabled.");
+                      } else {
+                          intentResultBuilder.setOemUiUsageStatus(IntentCreationResult
+                                  .OemUiUsageStatus.OEM_UI_CONFIG_SPECIFIED_FOUND_BUT_NOT_ENABLED);
+                          Slog.i(TAG,
+                                  "Found enabled oem CredMan UI component but it was not "
+                                      + "enabled.");
+                      }
                     }
-                } catch (PackageManager.NameNotFoundException e) {
+                } catch (RemoteException | PackageManager.NameNotFoundException e) {
                     intentResultBuilder.setOemUiUsageStatus(IntentCreationResult.OemUiUsageStatus
                             .OEM_UI_CONFIG_SPECIFIED_BUT_NOT_FOUND);
                     Slog.i(TAG, "Unable to find oem CredMan UI component: "
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 5f3c15d..4c9e73c 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -3202,7 +3202,8 @@
      */
     @FlaggedApi(Flags.FLAG_ADAPTIVE_HANDWRITING_BOUNDS)
     public final void setStylusHandwritingRegion(@NonNull Region handwritingRegion) {
-        if (handwritingRegion.equals(mLastHandwritingRegion)) {
+        final Region immutableHandwritingRegion = new Region(handwritingRegion);
+        if (immutableHandwritingRegion.equals(mLastHandwritingRegion)) {
             Log.v(TAG, "Failed to set setStylusHandwritingRegion():"
                     + " same region set twice.");
             return;
@@ -3210,10 +3211,10 @@
 
         if (DEBUG) {
             Log.d(TAG, "Setting new handwriting region for stylus handwriting "
-                    + handwritingRegion + " from last " + mLastHandwritingRegion);
+                    + immutableHandwritingRegion + " from last " + mLastHandwritingRegion);
         }
-        mPrivOps.setHandwritingTouchableRegion(handwritingRegion);
-        mLastHandwritingRegion = handwritingRegion;
+        mPrivOps.setHandwritingTouchableRegion(immutableHandwritingRegion);
+        mLastHandwritingRegion = immutableHandwritingRegion;
     }
 
     /**
diff --git a/core/java/android/net/http/X509TrustManagerExtensions.java b/core/java/android/net/http/X509TrustManagerExtensions.java
index b44f75a..3425b77 100644
--- a/core/java/android/net/http/X509TrustManagerExtensions.java
+++ b/core/java/android/net/http/X509TrustManagerExtensions.java
@@ -22,7 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
-import android.net.platform.flags.Flags;
+import android.security.Flags;
 import android.security.net.config.UserCertificateSource;
 
 import com.android.org.conscrypt.TrustManagerImpl;
@@ -152,7 +152,7 @@
      * @throws IllegalArgumentException if the TrustManager is not compatible.
      * @return the properly ordered chain used for verification as a list of X509Certificates.
      */
-    @FlaggedApi(Flags.FLAG_X509_EXTENSIONS_CERTIFICATE_TRANSPARENCY)
+    @FlaggedApi(Flags.FLAG_CERTIFICATE_TRANSPARENCY_CONFIGURATION)
     @NonNull
     public List<X509Certificate> checkServerTrusted(
             @SuppressLint("ArrayReturn") @NonNull X509Certificate[] chain,
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index b509c7a..230fa3f 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -1355,14 +1355,21 @@
 
     /**
      * @return true if we are blocked on a sync barrier
+     *
+     * Calls to this method must not be allowed to race with `next`.
+     * Specifically, the Looper thread must be paused before calling this method,
+     * and may not be resumed until after returning from this method.
      */
     boolean isBlockedOnSyncBarrier() {
         throwIfNotTest();
         if (mUseConcurrent) {
+            // Call nextMessage to get the stack drained into our priority queues
+            nextMessage(true);
+
             Iterator<MessageNode> queueIter = mPriorityQueue.iterator();
             MessageNode queueNode = iterateNext(queueIter);
 
-            if (queueNode.isBarrier()) {
+            if (queueNode != null && queueNode.isBarrier()) {
                 long now = SystemClock.uptimeMillis();
 
                 /* Look for a deliverable async node. If one exists we are not blocked. */
@@ -1375,14 +1382,12 @@
                  * Look for a deliverable sync node. In this case, if one exists we are blocked
                  * since the barrier prevents delivery of the Message.
                  */
-                while (queueNode.isBarrier()) {
+                while (queueNode != null && queueNode.isBarrier()) {
                     queueNode = iterateNext(queueIter);
                 }
                 if (queueNode != null && now >= queueNode.getWhen()) {
                     return true;
                 }
-
-                return false;
             }
         } else {
             Message msg = mMessages;
@@ -1409,10 +1414,8 @@
                 if (iter != null && now >= iter.when) {
                     return true;
                 }
-                return false;
             }
         }
-        /* No barrier was found. */
         return false;
     }
 
diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
index de0259e..d7d8e41 100644
--- a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
+++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
@@ -19,6 +19,7 @@
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.app.ActivityThread;
 import android.app.Instrumentation;
@@ -784,7 +785,7 @@
             mMessageDirectlyQueued = false;
             nativePollOnce(ptr, mNextPollTimeoutMillis);
 
-            Message msg = nextMessage();
+            Message msg = nextMessage(false);
             if (msg != null) {
                 msg.markInUse();
                 return msg;
@@ -1087,7 +1088,6 @@
      *
      * Caller must ensure that this doesn't race 'next' from the Looper thread.
      */
-    @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
     Long peekWhenForTest() {
         throwIfNotTest();
         Message ret = nextMessage(true);
@@ -1100,7 +1100,6 @@
      *
      * Caller must ensure that this doesn't race 'next' from the Looper thread.
      */
-    @SuppressLint("VisiblySynchronized") // Legacy MessageQueue synchronizes on this
     @Nullable
     Message pollForTest() {
         throwIfNotTest();
@@ -1109,13 +1108,21 @@
 
     /**
      * @return true if we are blocked on a sync barrier
+     *
+     * Calls to this method must not be allowed to race with `next`.
+     * Specifically, the Looper thread must be paused before calling this method,
+     * and may not be resumed until after returning from this method.
      */
     boolean isBlockedOnSyncBarrier() {
         throwIfNotTest();
+
+        // Call nextMessage to get the stack drained into our priority queues
+        nextMessage(true);
+
         Iterator<MessageNode> queueIter = mPriorityQueue.iterator();
         MessageNode queueNode = iterateNext(queueIter);
 
-        if (queueNode.isBarrier()) {
+        if (queueNode != null && queueNode.isBarrier()) {
             long now = SystemClock.uptimeMillis();
 
             /* Look for a deliverable async node. If one exists we are not blocked. */
@@ -1128,15 +1135,14 @@
              * Look for a deliverable sync node. In this case, if one exists we are blocked
              * since the barrier prevents delivery of the Message.
              */
-            while (queueNode.isBarrier()) {
+            while (queueNode != null && queueNode.isBarrier()) {
                 queueNode = iterateNext(queueIter);
             }
             if (queueNode != null && now >= queueNode.getWhen()) {
                 return true;
             }
-
-            return false;
         }
+        return false;
     }
 
     private StateNode getStateNode(StackNode node) {
@@ -1193,7 +1199,7 @@
         MessageNode p = (MessageNode) top;
 
         while (true) {
-            if (compare.compareMessage(p.mMessage, h, what, object, r, when)) {
+            if (compare.compareMessage(p, h, what, object, r, when)) {
                 found = true;
                 if (DEBUG) {
                     Log.w(TAG, "stackHasMessages node matches");
@@ -1238,7 +1244,7 @@
         while (iterator.hasNext()) {
             MessageNode msg = iterator.next();
 
-            if (compare.compareMessage(msg.mMessage, h, what, object, r, when)) {
+            if (compare.compareMessage(msg, h, what, object, r, when)) {
                 if (removeMatches) {
                     found = true;
                     if (queue.remove(msg)) {
diff --git a/core/java/android/os/EventLogTags.logtags b/core/java/android/os/EventLogTags.logtags
index b143a74..f57aad0 100644
--- a/core/java/android/os/EventLogTags.logtags
+++ b/core/java/android/os/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
 
 option java_package android.os
 
diff --git a/core/java/android/os/LegacyMessageQueue/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
index 5e1e1fd..c0333e9 100644
--- a/core/java/android/os/LegacyMessageQueue/MessageQueue.java
+++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
@@ -814,6 +814,10 @@
 
     /**
      * @return true if we are blocked on a sync barrier
+     *
+     * Calls to this method must not be allowed to race with `next`.
+     * Specifically, the Looper thread must be paused before calling this method,
+     * and may not be resumed until after returning from this method.
      */
     boolean isBlockedOnSyncBarrier() {
         throwIfNotTest();
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 7e73a5d..b9f2cfc 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -6501,7 +6501,11 @@
      * @hide
      */
     public static final void invalidateCacheOnUserDataChanged() {
-        if (android.multiuser.Flags.cacheProfilesReadOnly()) {
+        if (android.multiuser.Flags.cacheProfilesReadOnly()
+                || android.multiuser.Flags.cacheUserInfoReadOnly()) {
+            // TODO(b/383175685): Rename the invalidation call to make it clearer that it
+            // invalidates the caches for both getProfiles and getUserInfo (since they both use the
+            // same user_manager_user_data CachedProperty.api).
             UserManagerCache.invalidateProfiles();
         }
     }
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index aacc6e2..af96ccf 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -57,7 +57,7 @@
     is_exported: true
     is_fixed_read_only: true
     namespace: "permissions"
-    description: "enable enhanced confirmation incall apis"
+    description: "DEPRECATED, does not gate any apis"
     bug: "364535720"
 }
 
diff --git a/core/java/android/view/EventLogTags.logtags b/core/java/android/view/EventLogTags.logtags
index f379293..95894fa 100644
--- a/core/java/android/view/EventLogTags.logtags
+++ b/core/java/android/view/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
 
 option java_package android.view
 
@@ -35,7 +35,7 @@
 # 6: Percent
 # Default value for data of type int/long is 2 (bytes).
 #
-# See system/core/logcat/event.logtags for the master copy of the tags.
+# See system/logging/logcat/event.logtags for the master copy of the tags.
 
 # 32000 - 32999 reserved for input method framework
 # IME animation is started.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d15b0f5..d13f0e2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -28277,28 +28277,14 @@
         mPrivateFlags |= PFLAG_FORCE_LAYOUT;
         mPrivateFlags |= PFLAG_INVALIDATED;
 
-        if (mParent != null) {
-            if (!mParent.isLayoutRequested()) {
-                mParent.requestLayout();
-            } else {
-                clearMeasureCacheOfAncestors();
-            }
+        if (mParent != null && !mParent.isLayoutRequested()) {
+            mParent.requestLayout();
         }
         if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
             mAttachInfo.mViewRequestingLayout = null;
         }
     }
 
-    private void clearMeasureCacheOfAncestors() {
-        ViewParent parent = mParent;
-        while (parent instanceof View view) {
-            if (view.mMeasureCache != null) {
-                view.mMeasureCache.clear();
-            }
-            parent = view.mParent;
-        }
-    }
-
     /**
      * Forces this view to be laid out during the next layout pass.
      * This method does not call requestLayout() or forceLayout()
@@ -28654,10 +28640,8 @@
      */
     @RemotableViewMethod
     public void setMinimumHeight(int minHeight) {
-        if (mMinHeight != minHeight) {
-            mMinHeight = minHeight;
-            requestLayout();
-        }
+        mMinHeight = minHeight;
+        requestLayout();
     }
 
     /**
@@ -28687,10 +28671,8 @@
      */
     @RemotableViewMethod
     public void setMinimumWidth(int minWidth) {
-        if (mMinWidth != minWidth) {
-            mMinWidth = minWidth;
-            requestLayout();
-        }
+        mMinWidth = minWidth;
+        requestLayout();
 
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1596b85..c1b92ee3 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import static android.adpf.Flags.adpfViewrootimplActionDownBoost;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
 import static android.graphics.HardwareRenderer.SYNC_CONTEXT_IS_STOPPED;
@@ -7977,8 +7978,9 @@
         }
 
         private boolean moveFocusToAdjacentWindow(@FocusDirection int direction) {
-            if (getConfiguration().windowConfiguration.getWindowingMode()
-                    != WINDOWING_MODE_MULTI_WINDOW) {
+            final int windowingMode = getConfiguration().windowConfiguration.getWindowingMode();
+            if (windowingMode != WINDOWING_MODE_MULTI_WINDOW
+                    && windowingMode != WINDOWING_MODE_FREEFORM) {
                 return false;
             }
             try {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 6d89f3d..f82e5f9 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -948,7 +948,7 @@
                             // requestedVisibleTypes of WindowInsetsController by hiding the IME
                             final var statsToken = ImeTracker.forLogging().onStart(
                                     ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_CLIENT,
-                                    SoftInputShowHideReason.REASON_HIDE_WINDOW_LOST_FOCUS,
+                                    SoftInputShowHideReason.HIDE_WINDOW_LOST_FOCUS,
                                     false /* fromUser */);
                             if (DEBUG) {
                                 Log.d(TAG, "onWindowLostFocus, hiding IME because "
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 9c2833b..9fe3fd6 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -20,6 +20,7 @@
 import static android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO;
 import static android.appwidget.flags.Flags.drawDataParcel;
 import static android.appwidget.flags.Flags.remoteAdapterConversion;
+import static android.content.res.Flags.FLAG_SELF_TARGETING_ANDROID_RESOURCE_FRRO;
 import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
 import static android.util.proto.ProtoInputStream.NO_MORE_FIELDS;
 import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
@@ -8698,6 +8699,7 @@
          *
          * @hide
          */
+        @FlaggedApi(FLAG_SELF_TARGETING_ANDROID_RESOURCE_FRRO)
         @Nullable
         public static ColorResources createWithOverlay(Context context,
                 SparseIntArray colorMapping) {
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index a04071a..cfe44f8 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -414,6 +414,16 @@
 }
 
 flag {
+    name: "enable_desktop_recents_transitions_corners_bugfix"
+    namespace: "lse_desktop_experience"
+    description: "Enables rounded corners bugfix for Recents transitions."
+    bug: "383079261"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "enable_move_to_next_display_shortcut"
     namespace: "lse_desktop_experience"
     description: "Add new keyboard shortcut of moving a task into next display"
@@ -489,4 +499,15 @@
     namespace: "lse_desktop_experience"
     description: "Bugfixes / papercuts to bring Desktop Windowing to secondary displays."
     bug: "382023296"
+}
+
+flag {
+    name: "enable_top_visible_root_task_per_user_tracking"
+    namespace: "lse_desktop_experience"
+    description: "Enables tracking the top visible root tasks for a user."
+    bug: "381038076"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
 }
\ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 30f0c73..1c27515 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -455,6 +455,17 @@
 }
 
 flag {
+    name: "remove_defer_hiding_client"
+    namespace: "windowing_frontend"
+    description: "Remove mDeferHidingClient since everything is in shell-transition."
+    is_fixed_read_only: true
+    bug: "382485959"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
   name: "relative_insets"
   namespace: "windowing_frontend"
   description: "Support insets definition and calculation relative to task bounds."
diff --git a/core/java/com/android/internal/app/EventLogTags.logtags b/core/java/com/android/internal/app/EventLogTags.logtags
index d681a8d..a18a824 100644
--- a/core/java/com/android/internal/app/EventLogTags.logtags
+++ b/core/java/com/android/internal/app/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
 
 option java_package com.android.internal.app;
 
diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
index fa5cf2a..5d4e6a0 100644
--- a/core/java/com/android/internal/content/om/OverlayManagerImpl.java
+++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
@@ -36,6 +36,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.parsing.FrameworkParsingPackageUtils;
 import android.content.res.AssetManager;
+import android.content.res.Flags;
 import android.os.FabricatedOverlayInfo;
 import android.os.FabricatedOverlayInternal;
 import android.os.FabricatedOverlayInternalEntry;
@@ -235,17 +236,24 @@
         Preconditions.checkArgument(!entryList.isEmpty(), "overlay entries shouldn't be empty");
         final String overlayName = checkOverlayNameValid(overlayInternal.overlayName);
         checkPackageName(overlayInternal.packageName);
-        Preconditions.checkStringNotEmpty(overlayInternal.targetPackageName);
+        if (Flags.selfTargetingAndroidResourceFrro()) {
+            Preconditions.checkStringNotEmpty(overlayInternal.targetPackageName);
+        } else {
+            checkPackageName(overlayInternal.targetPackageName);
+            Preconditions.checkStringNotEmpty(
+                    overlayInternal.targetOverlayable,
+                    "Target overlayable should be neither null nor empty string.");
+        }
 
         final ApplicationInfo applicationInfo = mContext.getApplicationInfo();
         String targetPackage = null;
-        if (TextUtils.equals(overlayInternal.targetPackageName, "android")) {
+        if (Flags.selfTargetingAndroidResourceFrro() && TextUtils.equals(
+                overlayInternal.targetPackageName, "android")) {
             targetPackage = AssetManager.FRAMEWORK_APK_PATH;
         } else {
             targetPackage = Preconditions.checkStringNotEmpty(
                     applicationInfo.getBaseCodePath());
         }
-
         final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION);
         final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION);
 
diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
index 2a5593f..4d5e67a 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java
@@ -299,6 +299,12 @@
                 return "SHOW_SOFT_INPUT_IMM_DEPRECATION";
             case SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION:
                 return "CONTROL_WINDOW_INSETS_ANIMATION";
+            case SoftInputShowHideReason.SHOW_INPUT_TARGET_CHANGED:
+                return "SHOW_INPUT_TARGET_CHANGED";
+            case SoftInputShowHideReason.HIDE_INPUT_TARGET_CHANGED:
+                return "HIDE_INPUT_TARGET_CHANGED";
+            case SoftInputShowHideReason.HIDE_WINDOW_LOST_FOCUS:
+                return "HIDE_WINDOW_LOST_FOCUS";
             default:
                 return "Unknown=" + reason;
         }
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index 592ea9e..cf0580c 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -91,7 +91,7 @@
         SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION,
         SoftInputShowHideReason.SHOW_INPUT_TARGET_CHANGED,
         SoftInputShowHideReason.HIDE_INPUT_TARGET_CHANGED,
-        SoftInputShowHideReason.REASON_HIDE_WINDOW_LOST_FOCUS,
+        SoftInputShowHideReason.HIDE_WINDOW_LOST_FOCUS,
 })
 public @interface SoftInputShowHideReason {
     /** Default, undefined reason. */
@@ -340,18 +340,6 @@
     int HIDE_WINDOW_LEGACY_DIRECT = ImeProtoEnums.REASON_HIDE_WINDOW_LEGACY_DIRECT;
 
     /**
-     * Show soft input because the input target changed
-     * {@link com.android.server.wm.ImeInsetsSourceProvider#onInputTargetChanged}.
-     */
-    int SHOW_INPUT_TARGET_CHANGED = ImeProtoEnums.REASON_SHOW_INPUT_TARGET_CHANGED;
-
-    /**
-     * Hide soft input because the input target changed by
-     * {@link com.android.server.wm.ImeInsetsSourceProvider#onInputTargetChanged}.
-     */
-    int HIDE_INPUT_TARGET_CHANGED = ImeProtoEnums.REASON_HIDE_INPUT_TARGET_CHANGED;
-
-    /**
      * Show / Hide soft input by
      * {@link android.inputmethodservice.InputMethodService#resetStateForNewConfiguration}.
      */
@@ -420,6 +408,18 @@
      */
     int CONTROL_WINDOW_INSETS_ANIMATION = ImeProtoEnums.REASON_CONTROL_WINDOW_INSETS_ANIMATION;
 
+    /**
+     * Show soft input because the input target changed
+     * {@link com.android.server.wm.ImeInsetsSourceProvider#onInputTargetChanged}.
+     */
+    int SHOW_INPUT_TARGET_CHANGED = ImeProtoEnums.REASON_SHOW_INPUT_TARGET_CHANGED;
+
+    /**
+     * Hide soft input because the input target changed by
+     * {@link com.android.server.wm.ImeInsetsSourceProvider#onInputTargetChanged}.
+     */
+    int HIDE_INPUT_TARGET_CHANGED = ImeProtoEnums.REASON_HIDE_INPUT_TARGET_CHANGED;
+
     /** Hide soft input when the window lost focus. */
-    int REASON_HIDE_WINDOW_LOST_FOCUS = ImeProtoEnums.REASON_HIDE_WINDOW_LOST_FOCUS;
+    int HIDE_WINDOW_LOST_FOCUS = ImeProtoEnums.REASON_HIDE_WINDOW_LOST_FOCUS;
 }
diff --git a/core/java/com/android/internal/logging/EventLogTags.logtags b/core/java/com/android/internal/logging/EventLogTags.logtags
index 693bd16..db47797 100644
--- a/core/java/com/android/internal/logging/EventLogTags.logtags
+++ b/core/java/com/android/internal/logging/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
 
 option java_package com.android.internal.logging;
 
diff --git a/core/java/com/android/internal/widget/CallLayout.java b/core/java/com/android/internal/widget/CallLayout.java
index c852575..3a7c75a 100644
--- a/core/java/com/android/internal/widget/CallLayout.java
+++ b/core/java/com/android/internal/widget/CallLayout.java
@@ -30,7 +30,6 @@
 import android.view.RemotableViewMethod;
 import android.widget.FrameLayout;
 import android.widget.RemoteViews;
-import android.widget.TextView;
 import android.widget.flags.Flags;
 
 import com.android.internal.R;
@@ -59,7 +58,6 @@
     private CachingIconView mConversationIconView;
     private CachingIconView mIcon;
     private CachingIconView mConversationIconBadgeBg;
-    private TextView mConversationText;
 
     public CallLayout(@NonNull Context context) {
         super(context);
@@ -83,7 +81,6 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mPeopleHelper.init(getContext());
-        mConversationText = findViewById(R.id.conversation_text);
         mConversationIconView = findViewById(R.id.conversation_icon);
         mIcon = findViewById(R.id.icon);
         mConversationIconBadgeBg = findViewById(R.id.conversation_icon_badge_bg);
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 4b90420..b3ab5d3 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -777,37 +777,40 @@
 
         }
 
-        int conversationAvatarSize;
-        int facepileAvatarSize;
-        int facePileBackgroundSize;
-        if (mIsCollapsed) {
-            conversationAvatarSize = mConversationAvatarSize;
-            facepileAvatarSize = mFacePileAvatarSize;
-            facePileBackgroundSize = facepileAvatarSize + 2 * mFacePileProtectionWidth;
-        } else {
-            conversationAvatarSize = mConversationAvatarSizeExpanded;
-            facepileAvatarSize = mFacePileAvatarSizeExpandedGroup;
-            facePileBackgroundSize = facepileAvatarSize + 2 * mFacePileProtectionWidthExpanded;
+        if (!notificationsRedesignTemplates()) {
+            // We no longer need to update the size based on expansion state.
+            int conversationAvatarSize;
+            int facepileAvatarSize;
+            int facePileBackgroundSize;
+            if (mIsCollapsed) {
+                conversationAvatarSize = mConversationAvatarSize;
+                facepileAvatarSize = mFacePileAvatarSize;
+                facePileBackgroundSize = facepileAvatarSize + 2 * mFacePileProtectionWidth;
+            } else {
+                conversationAvatarSize = mConversationAvatarSizeExpanded;
+                facepileAvatarSize = mFacePileAvatarSizeExpandedGroup;
+                facePileBackgroundSize = facepileAvatarSize + 2 * mFacePileProtectionWidthExpanded;
+            }
+            LayoutParams layoutParams = (LayoutParams) mConversationFacePile.getLayoutParams();
+            layoutParams.width = conversationAvatarSize;
+            layoutParams.height = conversationAvatarSize;
+            mConversationFacePile.setLayoutParams(layoutParams);
+
+            layoutParams = (LayoutParams) bottomView.getLayoutParams();
+            layoutParams.width = facepileAvatarSize;
+            layoutParams.height = facepileAvatarSize;
+            bottomView.setLayoutParams(layoutParams);
+
+            layoutParams = (LayoutParams) topView.getLayoutParams();
+            layoutParams.width = facepileAvatarSize;
+            layoutParams.height = facepileAvatarSize;
+            topView.setLayoutParams(layoutParams);
+
+            layoutParams = (LayoutParams) bottomBackground.getLayoutParams();
+            layoutParams.width = facePileBackgroundSize;
+            layoutParams.height = facePileBackgroundSize;
+            bottomBackground.setLayoutParams(layoutParams);
         }
-        LayoutParams layoutParams = (LayoutParams) mConversationFacePile.getLayoutParams();
-        layoutParams.width = conversationAvatarSize;
-        layoutParams.height = conversationAvatarSize;
-        mConversationFacePile.setLayoutParams(layoutParams);
-
-        layoutParams = (LayoutParams) bottomView.getLayoutParams();
-        layoutParams.width = facepileAvatarSize;
-        layoutParams.height = facepileAvatarSize;
-        bottomView.setLayoutParams(layoutParams);
-
-        layoutParams = (LayoutParams) topView.getLayoutParams();
-        layoutParams.width = facepileAvatarSize;
-        layoutParams.height = facepileAvatarSize;
-        topView.setLayoutParams(layoutParams);
-
-        layoutParams = (LayoutParams) bottomBackground.getLayoutParams();
-        layoutParams.width = facePileBackgroundSize;
-        layoutParams.height = facePileBackgroundSize;
-        bottomBackground.setLayoutParams(layoutParams);
     }
 
     /**
@@ -832,6 +835,11 @@
      * update the icon position and sizing
      */
     private void updateIconPositionAndSize() {
+        if (notificationsRedesignTemplates()) {
+            // Icon size is fixed in the redesign.
+            return;
+        }
+
         int badgeProtrusion;
         int conversationAvatarSize;
         if (mIsOneToOne || mIsCollapsed) {
@@ -864,6 +872,11 @@
     }
 
     private void updatePaddingsBasedOnContentAvailability() {
+        if (notificationsRedesignTemplates()) {
+            // group icons have the same size as 1:1 conversations
+            return;
+        }
+
         // groups have avatars that need more spacing
         mMessagingLinearLayout.setSpacing(
                 mIsOneToOne ? mMessageSpacingStandard : mMessageSpacingGroup);
diff --git a/core/java/org/chromium/arc/EventLogTags.logtags b/core/java/org/chromium/arc/EventLogTags.logtags
index 1b7160e..8102d6f 100644
--- a/core/java/org/chromium/arc/EventLogTags.logtags
+++ b/core/java/org/chromium/arc/EventLogTags.logtags
@@ -1,4 +1,4 @@
-# See system/core/logcat/event.logtags for a description of the format of this file.
+# See system/logging/logcat/event.logtags for a description of the format of this file.
 
 option java_package org.chromium.arc
 
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 8e3303a..027113a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -92,7 +92,6 @@
         "android_view_VelocityTracker.cpp",
         "android_view_VerifiedKeyEvent.cpp",
         "android_view_VerifiedMotionEvent.cpp",
-        "com_android_internal_util_ArrayUtils.cpp",
         "com_android_internal_util_VirtualRefBasePtr.cpp",
         "core_jni_helpers.cpp",
         ":deviceproductinfoconstants_aidl",
@@ -264,6 +263,7 @@
                 "com_android_internal_os_ZygoteCommandBuffer.cpp",
                 "com_android_internal_os_ZygoteInit.cpp",
                 "com_android_internal_security_VerityUtils.cpp",
+                "com_android_internal_util_ArrayUtils.cpp",
                 "hwbinder/EphemeralStorage.cpp",
                 "fd_utils.cpp",
                 "android_hardware_input_InputWindowHandle.cpp",
diff --git a/core/res/res/layout/notification_2025_conversation_face_pile_layout.xml b/core/res/res/layout/notification_2025_conversation_face_pile_layout.xml
index b25adaa..68eafee 100644
--- a/core/res/res/layout/notification_2025_conversation_face_pile_layout.xml
+++ b/core/res/res/layout/notification_2025_conversation_face_pile_layout.xml
@@ -18,14 +18,14 @@
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/conversation_face_pile"
-    android:layout_width="@dimen/conversation_avatar_size"
-    android:layout_height="@dimen/conversation_avatar_size"
+    android:layout_width="@dimen/notification_2025_icon_circle_size"
+    android:layout_height="@dimen/notification_2025_icon_circle_size"
     android:forceHasOverlappingRendering="false"
     >
     <ImageView
         android:id="@+id/conversation_face_pile_top"
-        android:layout_width="@dimen/messaging_avatar_size"
-        android:layout_height="@dimen/messaging_avatar_size"
+        android:layout_width="@dimen/notification_2025_face_pile_avatar_size"
+        android:layout_height="@dimen/notification_2025_face_pile_avatar_size"
         android:scaleType="centerCrop"
         android:layout_gravity="end|top"
         android:background="@drawable/notification_icon_circle"
@@ -43,8 +43,8 @@
             />
         <ImageView
             android:id="@+id/conversation_face_pile_bottom"
-            android:layout_width="@dimen/messaging_avatar_size"
-            android:layout_height="@dimen/messaging_avatar_size"
+            android:layout_width="@dimen/notification_2025_face_pile_avatar_size"
+            android:layout_height="@dimen/notification_2025_face_pile_avatar_size"
             android:scaleType="centerCrop"
             android:layout_gravity="center"
             android:background="@drawable/notification_icon_circle"
diff --git a/core/res/res/layout/notification_2025_conversation_header.xml b/core/res/res/layout/notification_2025_conversation_header.xml
new file mode 100644
index 0000000..db79e79
--- /dev/null
+++ b/core/res/res/layout/notification_2025_conversation_header.xml
@@ -0,0 +1,171 @@
+<?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
+  -->
+
+<com.android.internal.widget.ConversationHeaderLinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/conversation_header"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:paddingTop="@dimen/notification_2025_margin"
+    >
+
+    <TextView
+        android:id="@+id/conversation_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+        android:textSize="16sp"
+        android:singleLine="true"
+        android:layout_weight="1"
+        />
+
+    <TextView
+        android:id="@+id/app_name_divider"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+        android:text="@string/notification_header_divider_symbol"
+        android:singleLine="true"
+        android:visibility="gone"
+        />
+
+    <!-- App Name -->
+    <com.android.internal.widget.ObservableTextView
+        android:id="@+id/app_name_text"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+        android:singleLine="true"
+        android:visibility="gone"
+        />
+
+    <TextView
+        android:id="@+id/time_divider"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+        android:text="@string/notification_header_divider_symbol"
+        android:singleLine="true"
+        android:visibility="gone"
+        />
+
+    <DateTimeView
+        android:id="@+id/time"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Time"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+        android:showRelative="true"
+        android:singleLine="true"
+        android:visibility="gone"
+        />
+
+    <ViewStub
+        android:id="@+id/chronometer"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+        android:layout="@layout/notification_template_part_chronometer"
+        android:visibility="gone"
+        />
+
+    <TextView
+        android:id="@+id/verification_divider"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+        android:text="@string/notification_header_divider_symbol"
+        android:singleLine="true"
+        android:visibility="gone"
+        />
+
+    <ImageView
+        android:id="@+id/verification_icon"
+        android:layout_width="@dimen/notification_verification_icon_size"
+        android:layout_height="@dimen/notification_verification_icon_size"
+        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+        android:baseline="10dp"
+        android:scaleType="fitCenter"
+        android:src="@drawable/ic_notifications_alerted"
+        android:visibility="gone"
+        />
+
+    <TextView
+        android:id="@+id/verification_text"
+        android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Info"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+        android:layout_weight="100"
+        android:showRelative="true"
+        android:singleLine="true"
+        android:visibility="gone"
+        />
+
+    <ImageButton
+        android:id="@+id/feedback"
+        android:layout_width="@dimen/notification_feedback_size"
+        android:layout_height="@dimen/notification_feedback_size"
+        android:layout_marginStart="@dimen/notification_header_separating_margin"
+        android:background="?android:selectableItemBackgroundBorderless"
+        android:contentDescription="@string/notification_feedback_indicator"
+        android:baseline="13dp"
+        android:scaleType="fitCenter"
+        android:src="@drawable/ic_feedback_indicator"
+        android:visibility="gone"
+        />
+
+    <ImageView
+        android:id="@+id/phishing_alert"
+        android:layout_width="@dimen/notification_phishing_alert_size"
+        android:layout_height="@dimen/notification_phishing_alert_size"
+        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+        android:baseline="10dp"
+        android:scaleType="fitCenter"
+        android:src="@drawable/ic_dialog_alert_material"
+        android:visibility="gone"
+        android:contentDescription="@string/notification_phishing_alert_content_description"
+        />
+
+    <ImageView
+        android:id="@+id/profile_badge"
+        android:layout_width="@dimen/notification_badge_size"
+        android:layout_height="@dimen/notification_badge_size"
+        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+        android:baseline="10dp"
+        android:scaleType="fitCenter"
+        android:visibility="gone"
+        android:contentDescription="@string/notification_work_profile_content_description"
+        />
+
+    <ImageView
+        android:id="@+id/alerted_icon"
+        android:layout_width="@dimen/notification_alerted_size"
+        android:layout_height="@dimen/notification_alerted_size"
+        android:layout_marginStart="@dimen/notification_conversation_header_separating_margin"
+        android:baseline="10dp"
+        android:contentDescription="@string/notification_alerted_content_description"
+        android:scaleType="fitCenter"
+        android:src="@drawable/ic_notifications_alerted"
+        android:visibility="gone"
+        />
+</com.android.internal.widget.ConversationHeaderLinearLayout>
diff --git a/core/res/res/layout/notification_2025_conversation_icon_container.xml b/core/res/res/layout/notification_2025_conversation_icon_container.xml
index 90befd9..7ec2450 100644
--- a/core/res/res/layout/notification_2025_conversation_icon_container.xml
+++ b/core/res/res/layout/notification_2025_conversation_icon_container.xml
@@ -18,32 +18,27 @@
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/conversation_icon_container"
-    android:layout_width="@dimen/conversation_content_start"
+    android:layout_width="@dimen/notification_2025_content_margin_start"
     android:layout_height="wrap_content"
     android:gravity="start|top"
     android:clipChildren="false"
     android:clipToPadding="false"
-    android:paddingTop="20dp"
-    android:paddingBottom="16dp"
     android:importantForAccessibility="no"
     >
 
     <FrameLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_margin="@dimen/notification_2025_margin"
         android:clipChildren="false"
         android:clipToPadding="false"
         android:layout_gravity="top|center_horizontal"
         >
 
-        <!-- Big icon: 48x48, 12dp padding top, 16dp padding sides -->
         <com.android.internal.widget.CachingIconView
             android:id="@+id/conversation_icon"
-            android:layout_width="@dimen/conversation_avatar_size"
-            android:layout_height="@dimen/conversation_avatar_size"
-            android:layout_marginLeft="@dimen/conversation_badge_protrusion"
-            android:layout_marginRight="@dimen/conversation_badge_protrusion"
-            android:layout_marginBottom="@dimen/conversation_badge_protrusion"
+            android:layout_width="@dimen/notification_2025_icon_circle_size"
+            android:layout_height="@dimen/notification_2025_icon_circle_size"
             android:background="@drawable/notification_icon_circle"
             android:clipToOutline="true"
             android:scaleType="centerCrop"
@@ -52,19 +47,25 @@
 
         <ViewStub
             android:layout="@layout/notification_2025_conversation_face_pile_layout"
-            android:layout_width="@dimen/conversation_avatar_size"
-            android:layout_height="@dimen/conversation_avatar_size"
-            android:layout_marginLeft="@dimen/conversation_badge_protrusion"
-            android:layout_marginRight="@dimen/conversation_badge_protrusion"
-            android:layout_marginBottom="@dimen/conversation_badge_protrusion"
+            android:layout_width="@dimen/notification_2025_icon_circle_size"
+            android:layout_height="@dimen/notification_2025_icon_circle_size"
             android:id="@+id/conversation_face_pile"
             />
 
+        <!-- The badge icon is visually aligned to the square containing the conversation icon,
+        but it has a border in the color of the background that is meant to delimit it from the
+        conversation icon. This border, although not visible due to the color, is technically
+        outside these bounds.
+        In order to align the badge properly to the bottom end of the square, we use a top/start
+        margin that is equal to (size of the conversation icon - size of the badge - size of the
+        border on one side).
+        -->
         <FrameLayout
             android:id="@+id/conversation_icon_badge"
-            android:layout_width="@dimen/conversation_icon_size_badged"
-            android:layout_height="@dimen/conversation_icon_size_badged"
-            android:layout_gravity="end|bottom"
+            android:layout_width="@dimen/notification_2025_conversation_icon_badge_size"
+            android:layout_height="@dimen/notification_2025_conversation_icon_badge_size"
+            android:layout_marginTop="@dimen/notification_2025_conversation_icon_badge_position"
+            android:layout_marginStart="@dimen/notification_2025_conversation_icon_badge_position"
             android:clipChildren="false"
             android:clipToPadding="false"
             >
@@ -83,7 +84,7 @@
                 android:id="@+id/icon"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
-                android:layout_margin="4dp"
+                android:layout_margin="@dimen/notification_2025_conversation_icon_badge_padding"
                 android:layout_gravity="center"
                 android:forceHasOverlappingRendering="false"
                 />
diff --git a/core/res/res/layout/notification_2025_template_collapsed_call.xml b/core/res/res/layout/notification_2025_template_collapsed_call.xml
index 614444d..c4bca11 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_call.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_call.xml
@@ -41,13 +41,13 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_weight="1"
-            android:layout_marginStart="@dimen/conversation_content_start"
+            android:layout_marginStart="@dimen/notification_2025_content_margin_start"
             android:orientation="vertical"
             android:paddingBottom="@dimen/notification_2025_margin"
             >
 
             <include
-                layout="@layout/notification_template_conversation_header"
+                layout="@layout/notification_2025_conversation_header"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 />
diff --git a/core/res/res/layout/notification_2025_template_conversation.xml b/core/res/res/layout/notification_2025_template_conversation.xml
index 0c4c7fb..f31f65e 100644
--- a/core/res/res/layout/notification_2025_template_conversation.xml
+++ b/core/res/res/layout/notification_2025_template_conversation.xml
@@ -60,11 +60,11 @@
                 <!-- Use layout_marginStart instead of paddingStart to work around strange
                      measurement behavior on lower display densities. -->
                 <include
-                    layout="@layout/notification_template_conversation_header"
+                    layout="@layout/notification_2025_conversation_header"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     android:layout_marginBottom="2dp"
-                    android:layout_marginStart="@dimen/conversation_content_start"
+                    android:layout_marginStart="@dimen/notification_2025_content_margin_start"
                     />
 
                 <!-- Messages -->
@@ -86,7 +86,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="@dimen/notification_content_margin"
-            android:layout_marginStart="@dimen/conversation_content_start"
+            android:layout_marginStart="@dimen/notification_2025_content_margin_start"
             android:layout_marginEnd="@dimen/notification_content_margin_end" />
         <include layout="@layout/notification_material_action_list" />
     </com.android.internal.widget.RemeasuringLinearLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_call.xml b/core/res/res/layout/notification_2025_template_expanded_call.xml
index 3ff71b7..2af0ec2 100644
--- a/core/res/res/layout/notification_2025_template_expanded_call.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_call.xml
@@ -49,13 +49,13 @@
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
-                android:layout_marginStart="@dimen/conversation_content_start"
+                android:layout_marginStart="@dimen/notification_2025_content_margin_start"
                 android:orientation="vertical"
                 android:minHeight="68dp"
                 >
 
                 <include
-                    layout="@layout/notification_template_conversation_header"
+                    layout="@layout/notification_2025_conversation_header"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
                     />
@@ -97,7 +97,7 @@
             layout="@layout/notification_template_smart_reply_container"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/notification_content_margin_start"
+            android:layout_marginStart="@dimen/notification_2025_content_margin_start"
             android:layout_marginEnd="@dimen/notification_content_margin_end"
             android:layout_marginTop="@dimen/notification_content_margin"
             />
diff --git a/core/res/res/layout/notification_2025_text.xml b/core/res/res/layout/notification_2025_text.xml
index 48b1083..474f6d2 100644
--- a/core/res/res/layout/notification_2025_text.xml
+++ b/core/res/res/layout/notification_2025_text.xml
@@ -21,6 +21,7 @@
     android:layout_height="@dimen/notification_text_height"
     android:layout_gravity="top"
     android:layout_marginTop="@dimen/notification_text_margin_top"
+    android:ellipsize="end"
     android:fadingEdge="horizontal"
     android:gravity="top"
     android:maxLines="1"
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index f53acbf..51bd4cc 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -912,6 +912,8 @@
     <dimen name="conversation_icon_size_badged">20dp</dimen>
     <!-- size of the conversation avatar in an expanded group -->
     <dimen name="conversation_avatar_size_group_expanded">@dimen/messaging_avatar_size</dimen>
+    <!-- size of the face pile icons (2025 redesign version) -->
+    <dimen name="notification_2025_face_pile_avatar_size">24dp</dimen>
     <!-- size of the face pile icons -->
     <dimen name="conversation_face_pile_avatar_size">32dp</dimen>
     <!-- size of the face pile icons when the group is expanded -->
@@ -939,6 +941,18 @@
     <!-- The size of the importance ring -->
     <dimen name="importance_ring_size">20dp</dimen>
 
+    <!-- The spacing around the app icon badge shown next to the conversation icon -->
+    <dimen name="notification_2025_conversation_icon_badge_padding">2dp</dimen>
+
+    <!-- Top and start margin for the app icon badge shown next to the conversation icon, to align
+        it to the bottom end corner.
+        40dp (conversation icon size) - 16dp (actual size of badge) - 2dp (badge padding) -->
+    <dimen name="notification_2025_conversation_icon_badge_position">22dp</dimen>
+
+    <!-- The size of the app icon badge shown next to the conversation icon, including its padding.
+        The actual size of the icon is 16dp, plus 2dp for each side for the padding. -->
+    <dimen name="notification_2025_conversation_icon_badge_size">20dp</dimen>
+
     <!-- The top padding of the conversation icon container in the regular state-->
     <dimen name="conversation_icon_container_top_padding">20dp</dimen>
 
diff --git a/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
index 6d2dd53..ff3abae 100644
--- a/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
+++ b/core/tests/coretests/src/android/content/pm/parsing/ApkLiteParseUtilsTest.java
@@ -33,7 +33,6 @@
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.util.ArraySet;
 import android.util.PackageUtils;
 
@@ -62,7 +61,6 @@
 import java.util.Set;
 
 @Presubmit
-@RequiresFlagsEnabled(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
 public class ApkLiteParseUtilsTest {
 
     @Rule
diff --git a/core/tests/coretests/src/android/view/ViewGroupTest.java b/core/tests/coretests/src/android/view/ViewGroupTest.java
index 43c404e..ae3ad36 100644
--- a/core/tests/coretests/src/android/view/ViewGroupTest.java
+++ b/core/tests/coretests/src/android/view/ViewGroupTest.java
@@ -213,35 +213,6 @@
         assertTrue(autofillableViews.containsAll(Arrays.asList(viewA, viewC)));
     }
 
-    @Test
-    public void testMeasureCache() {
-        final int spec1 = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.AT_MOST);
-        final int spec2 = View.MeasureSpec.makeMeasureSpec(50, View.MeasureSpec.AT_MOST);
-        final Context context = getInstrumentation().getContext();
-        final View child = new View(context);
-        final TestView parent = new TestView(context, 0);
-        parent.addView(child);
-
-        child.setPadding(1, 2, 3, 4);
-        parent.measure(spec1, spec1);
-        assertEquals(4, parent.getMeasuredWidth());
-        assertEquals(6, parent.getMeasuredHeight());
-
-        child.setPadding(5, 6, 7, 8);
-        parent.measure(spec2, spec2);
-        assertEquals(12, parent.getMeasuredWidth());
-        assertEquals(14, parent.getMeasuredHeight());
-
-        // This ends the state of forceLayout.
-        parent.layout(0, 0, 50, 50);
-
-        // The cached values should be cleared after the new setPadding is called. And the measured
-        // width and height should be up-to-date.
-        parent.measure(spec1, spec1);
-        assertEquals(12, parent.getMeasuredWidth());
-        assertEquals(14, parent.getMeasuredHeight());
-    }
-
     private static void getUnobscuredTouchableRegion(Region outRegion, View view) {
         outRegion.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
         final ViewParent parent = view.getParent();
@@ -269,19 +240,6 @@
         protected void onLayout(boolean changed, int l, int t, int r, int b) {
             // We don't layout this view.
         }
-
-        @Override
-        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-            int measuredWidth = 0;
-            int measuredHeight = 0;
-            final int count = getChildCount();
-            for (int i = 0; i < count; i++) {
-                final View child = getChildAt(i);
-                measuredWidth += child.getPaddingLeft() + child.getPaddingRight();
-                measuredHeight += child.getPaddingTop() + child.getPaddingBottom();
-            }
-            setMeasuredDimension(measuredWidth, measuredHeight);
-        }
     }
 
     public static class AutofillableTestView extends TestView {
diff --git a/core/tests/overlaytests/device_self_targeting/Android.bp b/core/tests/overlaytests/device_self_targeting/Android.bp
index 931eac5..14a3cdf 100644
--- a/core/tests/overlaytests/device_self_targeting/Android.bp
+++ b/core/tests/overlaytests/device_self_targeting/Android.bp
@@ -31,6 +31,7 @@
         "androidx.test.ext.junit",
         "mockito-target-minus-junit4",
         "truth",
+        "flag-junit",
     ],
 
     optimize: {
diff --git a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
index 28d6545..bcf1446 100644
--- a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
+++ b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
@@ -16,6 +16,7 @@
 
 package com.android.overlaytest;
 
+import static android.content.res.Flags.FLAG_SELF_TARGETING_ANDROID_RESOURCE_FRRO;
 import static android.content.Context.MODE_PRIVATE;
 import static android.content.pm.PackageManager.SIGNATURE_NO_MATCH;
 
@@ -41,6 +42,8 @@
 import android.os.FabricatedOverlayInternalEntry;
 import android.os.ParcelFileDescriptor;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.util.Log;
 import android.util.Pair;
 import android.util.TypedValue;
@@ -76,6 +79,8 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class OverlayManagerImplTest {
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final String TAG = "OverlayManagerImplTest";
 
     private static final String TARGET_COLOR_RES = "color/mycolor";
@@ -210,6 +215,22 @@
     }
 
     @Test
+    @DisableFlags(FLAG_SELF_TARGETING_ANDROID_RESOURCE_FRRO)
+    public void registerOverlay_forAndroidPackage_shouldFail() {
+        FabricatedOverlayInternal overlayInternal =
+                createOverlayWithName(
+                        mOverlayName,
+                        SYSTEM_APP_OVERLAYABLE,
+                        "android",
+                        List.of(Pair.create("color/white", Pair.create(null, Color.BLACK))));
+
+        assertThrows(
+                "Wrong target package name",
+                IllegalArgumentException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
     public void getOverlayInfosForTarget_defaultShouldBeZero() {
         List<OverlayInfo> overlayInfos =
                 mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
diff --git a/core/tests/overlaytests/host/Android.bp b/core/tests/overlaytests/host/Android.bp
index 6340980..9b72004 100644
--- a/core/tests/overlaytests/host/Android.bp
+++ b/core/tests/overlaytests/host/Android.bp
@@ -28,14 +28,14 @@
     test_suites: [
         "device-tests",
     ],
-    target_required: [
-        "OverlayHostTests_NonPlatformSignatureOverlay",
-        "OverlayHostTests_PlatformSignatureStaticOverlay",
-        "OverlayHostTests_PlatformSignatureOverlay",
-        "OverlayHostTests_UpdateOverlay",
-        "OverlayHostTests_FrameworkOverlayV1",
-        "OverlayHostTests_FrameworkOverlayV2",
-        "OverlayHostTests_AppOverlayV1",
-        "OverlayHostTests_AppOverlayV2",
+    device_common_data: [
+        ":OverlayHostTests_NonPlatformSignatureOverlay",
+        ":OverlayHostTests_PlatformSignatureStaticOverlay",
+        ":OverlayHostTests_PlatformSignatureOverlay",
+        ":OverlayHostTests_UpdateOverlay",
+        ":OverlayHostTests_FrameworkOverlayV1",
+        ":OverlayHostTests_FrameworkOverlayV2",
+        ":OverlayHostTests_AppOverlayV1",
+        ":OverlayHostTests_AppOverlayV2",
     ],
 }
diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_button_dark.xml
deleted file mode 100644
index f3800e0..0000000
--- a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_button_dark.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
-  -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:height="24dp"
-    android:width="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-    <path
-        android:fillColor="#000000"
-        android:pathData="M5,5H10V7H7V10H5V5M14,5H19V10H17V7H14V5M17,14H19V19H14V17H17V14M10,17V19H5V14H7V17H10Z"/>
-</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_exit_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_exit_button_dark.xml
deleted file mode 100644
index 5260450..0000000
--- a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_exit_button_dark.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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.
-  -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="960"
-    android:viewportHeight="960"
-    android:tint="?attr/colorControlNormal">
-    <path
-        android:fillColor="@android:color/white"
-        android:pathData="M240,840L240,720L120,720L120,640L320,640L320,840L240,840ZM640,840L640,640L840,640L840,720L720,720L720,840L640,840ZM120,320L120,240L240,240L240,120L320,120L320,320L120,320ZM640,320L640,120L720,120L720,240L840,240L840,320L640,320Z"/>
-</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_or_maximize_exit_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_or_maximize_exit_button_dark.xml
new file mode 100644
index 0000000..b6289e2
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_immersive_or_maximize_exit_button_dark.xml
@@ -0,0 +1,26 @@
+<?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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/black"
+        android:pathData="M520,560L600,560L600,560ZM320,720Q287,720 263.5,696.5Q240,673 240,640L240,160Q240,127 263.5,103.5Q287,80 320,80L800,80Q833,80 856.5,103.5Q880,127 880,160L880,640Q880,673 856.5,696.5Q833,720 800,720L320,720ZM320,640L800,640Q800,640 800,640Q800,640 800,640L800,160Q800,160 800,160Q800,160 800,160L320,160Q320,160 320,160Q320,160 320,160L320,640Q320,640 320,640Q320,640 320,640ZM160,880Q127,880 103.5,856.5Q80,833 80,800L80,240L160,240L160,800Q160,800 160,800Q160,800 160,800L720,800L720,880L160,880ZM320,160L320,160Q320,160 320,160Q320,160 320,160L320,640Q320,640 320,640Q320,640 320,640L320,640Q320,640 320,640Q320,640 320,640L320,160Q320,160 320,160Q320,160 320,160Z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
index f9f43bc..86be0d4 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
@@ -159,7 +159,8 @@
      * {@link PersistentSnapPosition} + {@link #NOT_IN_SPLIT}.
      */
     @IntDef(value = {
-            NOT_IN_SPLIT,
+            NOT_IN_SPLIT, // user is not in split screen
+            SNAP_TO_NONE, // in "free snap mode," where apps are fully resizable
             SNAP_TO_2_33_66,
             SNAP_TO_2_50_50,
             SNAP_TO_2_66_33,
@@ -171,6 +172,23 @@
     })
     public @interface SplitScreenState {}
 
+    /** Converts a {@link SplitScreenState} to a human-readable string. */
+    public static String stateToString(@SplitScreenState int state) {
+        return switch (state) {
+            case NOT_IN_SPLIT -> "NOT_IN_SPLIT";
+            case SNAP_TO_NONE -> "SNAP_TO_NONE";
+            case SNAP_TO_2_33_66 -> "SNAP_TO_2_33_66";
+            case SNAP_TO_2_50_50 -> "SNAP_TO_2_50_50";
+            case SNAP_TO_2_66_33 -> "SNAP_TO_2_66_33";
+            case SNAP_TO_2_90_10 -> "SNAP_TO_2_90_10";
+            case SNAP_TO_2_10_90 -> "SNAP_TO_2_10_90";
+            case SNAP_TO_3_33_33_33 -> "SNAP_TO_3_33_33_33";
+            case SNAP_TO_3_45_45_10 -> "SNAP_TO_3_45_45_10";
+            case SNAP_TO_3_10_45_45 -> "SNAP_TO_3_10_45_45";
+            default -> "UNKNOWN";
+        };
+    }
+
     /**
      * Checks if the snapPosition in question is a {@link PersistentSnapPosition}.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
index 7243ea3..68c42d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
@@ -18,6 +18,8 @@
 
 package com.android.wm.shell.apptoweb
 
+import android.app.assist.AssistContent
+import android.app.assist.AssistContent.EXTRA_SESSION_TRANSFER_WEB_URI
 import android.content.Context
 import android.content.Intent
 import android.content.Intent.ACTION_VIEW
@@ -102,3 +104,10 @@
         return null
     }
 }
+
+/**
+ * Returns the web uri from the given [AssistContent].
+ */
+fun AssistContent.getSessionWebUri(): Uri? {
+    return extras.getParcelable(EXTRA_SESSION_TRANSFER_WEB_URI) ?: webUri
+}
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 60a52a8..56efdb8 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
@@ -1314,7 +1314,7 @@
                 }
             }
 
-            if (handlePrepareTransition(info, st, ft, finishCallback)) {
+            if (handlePrepareTransition(transition, info, st, ft, finishCallback)) {
                 if (checkTakeoverFlags()) {
                     mTakeoverHandler = mTransitions.getHandlerForTakeover(transition, info);
                 }
@@ -1630,7 +1630,7 @@
          * happen when core make an activity become visible.
          */
         @VisibleForTesting
-        boolean handlePrepareTransition(
+        boolean handlePrepareTransition(@NonNull IBinder transition,
                 @NonNull TransitionInfo info,
                 @NonNull SurfaceControl.Transaction st,
                 @NonNull SurfaceControl.Transaction ft,
@@ -1678,6 +1678,8 @@
                 }
             }
             st.apply();
+            // In case other transition handler took the handleRequest before this class.
+            mPrepareOpenTransition = transition;
             mFinishOpenTransaction = ft;
             mFinishOpenTransitionCallback = finishCallback;
             mOpenTransitionInfo = info;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 5129d83..c74bf53 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -338,6 +338,11 @@
             // Make mImeSourceControl point to the new control before starting the animation.
             if (hadImeSourceControl && mImeSourceControl != imeSourceControl) {
                 mImeSourceControl.release(SurfaceControl::release);
+                if (android.view.inputmethod.Flags.refactorInsetsController()
+                        && !hasImeLeash && mAnimation != null) {
+                    // In case of losing the leash, the animation should be cancelled.
+                    mAnimation.cancel();
+                }
             }
             mImeSourceControl = imeSourceControl;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index 813772f..2f5afca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -352,8 +352,8 @@
                 ? mPinnedTaskbarInsets.right : mPinnedTaskbarInsets.bottom;
 
         float ratio = areOffscreenRatiosSupported()
-                ? SplitLayout.OFFSCREEN_ASYMMETRIC_RATIO
-                : SplitLayout.ONSCREEN_ONLY_ASYMMETRIC_RATIO;
+                ? SplitSpec.OFFSCREEN_ASYMMETRIC_RATIO
+                : SplitSpec.ONSCREEN_ONLY_ASYMMETRIC_RATIO;
         int size = (int) (ratio * (end - start)) - mDividerSize / 2;
 
         int leftTopPosition = start + pinnedTaskbarShiftStart + size;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index e6b6ef7..88c91db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -112,11 +112,6 @@
     private static final int FLING_EXIT_DURATION = 450;
     private static final int FLING_OFFSCREEN_DURATION = 500;
 
-    /** A split ratio used on larger screens, where we can fit both apps onscreen. */
-    public static final float ONSCREEN_ONLY_ASYMMETRIC_RATIO = 0.33f;
-    /** A split ratio used on smaller screens, where we place one app mostly offscreen. */
-    public static final float OFFSCREEN_ASYMMETRIC_RATIO = 0.1f;
-
     // Here are some (arbitrarily decided) layer definitions used during animations to make sure the
     // layers stay in order. (During transitions, everything is reparented onto a transition root
     // and can be freely relayered.)
@@ -236,7 +231,7 @@
         updateDividerConfig(mContext);
 
         mRootBounds.set(configuration.windowConfiguration.getBounds());
-        mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
+        updateLayouts();
         mInteractionJankMonitor = InteractionJankMonitor.getInstance();
         resetDividerPosition();
         updateInvisibleRect();
@@ -490,7 +485,7 @@
         mIsLargeScreen = configuration.smallestScreenWidthDp >= 600;
         mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
                 configuration);
-        mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
+        updateLayouts();
         updateDividerConfig(mContext);
         initDividerPosition(mTempRect, wasLeftRightSplit);
         updateInvisibleRect();
@@ -518,7 +513,7 @@
         mRootBounds.set(tmpRect);
         mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
                 mIsLargeScreen, mRootBounds.width() >= mRootBounds.height());
-        mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
+        updateLayouts();
         initDividerPosition(mTempRect, wasLeftRightSplit);
     }
 
@@ -652,7 +647,7 @@
         if (!mPinnedTaskbarInsets.equals(pinnedTaskbarInsets)) {
             mPinnedTaskbarInsets = pinnedTaskbarInsets;
             // Refresh the DividerSnapAlgorithm.
-            mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
+            updateLayouts();
             // If the divider is no longer placed on a snap point, animate it to the nearest one.
             DividerSnapAlgorithm.SnapTarget snapTarget =
                     findSnapTarget(mDividerPosition, 0, false /* hardDismiss */);
@@ -824,8 +819,22 @@
         return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss);
     }
 
-    private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds) {
-        final Rect insets = getDisplayStableInsets(context);
+    /**
+     * (Re)calculates the split screen logic for this particular display/orientation. Refreshes the
+     * DividerSnapAlgorithm, which controls divider snap points, and populates a map in SplitState
+     * with bounds for all valid split layouts.
+     */
+    private void updateLayouts() {
+        // Update SplitState map
+
+        if (Flags.enableFlexibleTwoAppSplit()) {
+            mSplitState.populateLayouts(
+                    mRootBounds, mDividerSize, mIsLeftRightSplit, mPinnedTaskbarInsets.toRect());
+        }
+
+        // Get new DividerSnapAlgorithm
+
+        final Rect insets = getDisplayStableInsets(mContext);
 
         // Make split axis insets value same as the larger one to avoid bounds1 and bounds2
         // have difference for avoiding size-compat mode when switching unresizable apps in
@@ -835,10 +844,10 @@
             insets.set(insets.left, largerInsets, insets.right, largerInsets);
         }
 
-        return new DividerSnapAlgorithm(
-                context.getResources(),
-                rootBounds.width(),
-                rootBounds.height(),
+        mDividerSnapAlgorithm = new DividerSnapAlgorithm(
+                mContext.getResources(),
+                mRootBounds.width(),
+                mRootBounds.height(),
                 mDividerSize,
                 mIsLeftRightSplit,
                 insets,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java
new file mode 100644
index 0000000..9c951bd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java
@@ -0,0 +1,183 @@
+/*
+ * 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.wm.shell.common.split;
+
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_10_45_45;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_33_33_33;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_45_45_10;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.stateToString;
+
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.Log;
+
+import com.android.wm.shell.shared.split.SplitScreenConstants.SplitScreenState;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A reference class that stores the split layouts available in this device/orientation. Layouts are
+ * available as lists of RectFs, where each RectF represents the bounds of an app.
+ */
+public class SplitSpec {
+    private static final String TAG = "SplitSpec";
+    private static final boolean DEBUG = true;
+
+    /** A split ratio used on larger screens, where we can fit both apps onscreen. */
+    public static final float ONSCREEN_ONLY_ASYMMETRIC_RATIO = 0.33f;
+    /** A split ratio used on smaller screens, where we place one app mostly offscreen. */
+    public static final float OFFSCREEN_ASYMMETRIC_RATIO = 0.1f;
+    /** A 50-50 split ratio. */
+    public static final float MIDDLE_RATIO = 0.5f;
+
+    private final boolean mIsLeftRightSplit;
+    /** The usable display area, considering insets that affect split bounds. */
+    private final RectF mUsableArea;
+    /** Half the divider size. */
+    private final float mHalfDiv;
+
+    /** A large map that stores all valid split layouts. */
+    private final Map<Integer, List<RectF>> mLayouts = new HashMap<>();
+
+    /** Constructor; initializes the layout map. */
+    public SplitSpec(Rect displayBounds, int dividerSize, boolean isLeftRightSplit,
+            Rect pinnedTaskbarInsets) {
+        mIsLeftRightSplit = isLeftRightSplit;
+        mUsableArea = new RectF(displayBounds);
+        mUsableArea.left += pinnedTaskbarInsets.left;
+        mUsableArea.top += pinnedTaskbarInsets.top;
+        mUsableArea.right -= pinnedTaskbarInsets.right;
+        mUsableArea.bottom -= pinnedTaskbarInsets.bottom;
+        mHalfDiv = dividerSize / 2f;
+
+        // The "start" position, considering insets.
+        float s = isLeftRightSplit ? mUsableArea.left : mUsableArea.top;
+        // The "end" position, considering insets.
+        float e = isLeftRightSplit ? mUsableArea.right : mUsableArea.bottom;
+        // The "length" of the usable display (width or height). Apps are arranged along this axis.
+        float l = e - s;
+        float divPos;
+        float divPos2;
+
+        // SNAP_TO_2_10_90
+        divPos = s + (l * OFFSCREEN_ASYMMETRIC_RATIO);
+        createAppLayout(SNAP_TO_2_10_90, divPos);
+
+        // SNAP_TO_2_33_66
+        divPos = s + (l * ONSCREEN_ONLY_ASYMMETRIC_RATIO);
+        createAppLayout(SNAP_TO_2_33_66, divPos);
+
+        // SNAP_TO_2_50_50
+        divPos = s + (l * MIDDLE_RATIO);
+        createAppLayout(SNAP_TO_2_50_50, divPos);
+
+        // SNAP_TO_2_66_33
+        divPos = s + (l * (1 - ONSCREEN_ONLY_ASYMMETRIC_RATIO));
+        createAppLayout(SNAP_TO_2_66_33, divPos);
+
+        // SNAP_TO_2_90_10
+        divPos = s + (l * (1 - OFFSCREEN_ASYMMETRIC_RATIO));
+        createAppLayout(SNAP_TO_2_90_10, divPos);
+
+        // SNAP_TO_3_10_45_45
+        divPos = s + (l * OFFSCREEN_ASYMMETRIC_RATIO);
+        divPos2 = e - ((l * (1 - OFFSCREEN_ASYMMETRIC_RATIO)) / 2f);
+        createAppLayout(SNAP_TO_3_10_45_45, divPos, divPos2);
+
+        // SNAP_TO_3_33_33_33
+        divPos = s + (l * ONSCREEN_ONLY_ASYMMETRIC_RATIO);
+        divPos2 = e - (l * ONSCREEN_ONLY_ASYMMETRIC_RATIO);
+        createAppLayout(SNAP_TO_3_33_33_33, divPos, divPos2);
+
+        // SNAP_TO_3_45_45_10
+        divPos = s + ((l * (1 - OFFSCREEN_ASYMMETRIC_RATIO)) / 2f);
+        divPos2 = e - (l * OFFSCREEN_ASYMMETRIC_RATIO);
+        createAppLayout(SNAP_TO_3_45_45_10, divPos, divPos2);
+
+        if (DEBUG) {
+            dump();
+        }
+    }
+
+    /**
+     * Creates a two-app layout and enters it into the layout map.
+     * @param divPos The position of the divider.
+     */
+    private void createAppLayout(@SplitScreenState int state, float divPos) {
+        List<RectF> list = new ArrayList<>();
+        RectF rect1 = new RectF(mUsableArea);
+        RectF rect2 = new RectF(mUsableArea);
+        if (mIsLeftRightSplit) {
+            rect1.right = divPos - mHalfDiv;
+            rect2.left = divPos + mHalfDiv;
+        } else {
+            rect1.top = divPos - mHalfDiv;
+            rect2.bottom = divPos + mHalfDiv;
+        }
+        list.add(rect1);
+        list.add(rect2);
+        mLayouts.put(state, list);
+    }
+
+    /**
+     * Creates a three-app layout and enters it into the layout map.
+     * @param divPos1 The position of the first divider.
+     * @param divPos2 The position of the second divider.
+     */
+    private void createAppLayout(@SplitScreenState int state, float divPos1, float divPos2) {
+        List<RectF> list = new ArrayList<>();
+        RectF rect1 = new RectF(mUsableArea);
+        RectF rect2 = new RectF(mUsableArea);
+        RectF rect3 = new RectF(mUsableArea);
+        if (mIsLeftRightSplit) {
+            rect1.right = divPos1 - mHalfDiv;
+            rect2.left = divPos1 + mHalfDiv;
+            rect2.right = divPos2 - mHalfDiv;
+            rect3.left = divPos2 + mHalfDiv;
+        } else {
+            rect1.right = divPos1 - mHalfDiv;
+            rect2.left = divPos1 + mHalfDiv;
+            rect3.right = divPos2 - mHalfDiv;
+            rect3.left = divPos2 + mHalfDiv;
+        }
+        list.add(rect1);
+        list.add(rect2);
+        list.add(rect3);
+        mLayouts.put(state, list);
+    }
+
+    /** Logs all calculated layouts */
+    private void dump() {
+        mLayouts.forEach((k, v) -> {
+            Log.d(TAG, stateToString(k));
+            v.forEach(rect -> Log.d(TAG, " - " + rect.toShortString()));
+        });
+    }
+
+    /** Returns the layout associated with a given split state. */
+    List<RectF> getSpec(@SplitScreenState int state) {
+        return mLayouts.get(state);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java
index 71758e0..d1d133d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitState.java
@@ -19,11 +19,17 @@
 import static com.android.wm.shell.shared.split.SplitScreenConstants.NOT_IN_SPLIT;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SplitScreenState;
 
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import java.util.List;
+
 /**
  * A class that manages the "state" of split screen. See {@link SplitScreenState} for definitions.
  */
 public class SplitState {
     private @SplitScreenState int mState = NOT_IN_SPLIT;
+    private SplitSpec mSplitSpec;
 
     /** Updates the current state of split screen on this device. */
     public void set(@SplitScreenState int newState) {
@@ -39,4 +45,16 @@
     public void exit() {
         set(NOT_IN_SPLIT);
     }
+
+    /** Refresh the valid layouts for this display/orientation. */
+    public void populateLayouts(Rect displayBounds, int dividerSize, boolean isLeftRightSplit,
+            Rect pinnedTaskbarInsets) {
+        mSplitSpec =
+                new SplitSpec(displayBounds, dividerSize, isLeftRightSplit, pinnedTaskbarInsets);
+    }
+
+    /** Returns the layout associated with a given split state. */
+    public List<RectF> getLayout(@SplitScreenState int state) {
+        return mSplitSpec.getSpec(state);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
index 8e2a412..536dc2a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
@@ -74,13 +74,11 @@
         { SurfaceControl.Transaction() },
     )
 
-    @VisibleForTesting var state: TransitionState? = null
-
-    @VisibleForTesting val pendingExternalExitTransitions = mutableListOf<ExternalPendingExit>()
+    @VisibleForTesting val pendingImmersiveTransitions = mutableListOf<PendingTransition>()
 
     /** Whether there is an immersive transition that hasn't completed yet. */
     private val inProgress: Boolean
-        get() = state != null || pendingExternalExitTransitions.isNotEmpty()
+        get() = pendingImmersiveTransitions.isNotEmpty()
 
     private val rectEvaluator = RectEvaluator()
 
@@ -101,20 +99,19 @@
         if (inProgress) {
             logV(
                 "Cannot start entry because transition(s) already in progress: %s",
-                getRunningTransitions(),
+                pendingImmersiveTransitions,
             )
             return
         }
         val wct = WindowContainerTransaction().apply { setBounds(taskInfo.token, Rect()) }
         logV("Moving task ${taskInfo.taskId} into immersive mode")
         val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this)
-        state =
-            TransitionState(
-                transition = transition,
-                displayId = taskInfo.displayId,
-                taskId = taskInfo.taskId,
-                direction = Direction.ENTER,
-            )
+        addPendingImmersiveTransition(
+            taskId = taskInfo.taskId,
+            displayId = taskInfo.displayId,
+            direction = Direction.ENTER,
+            transition = transition,
+        )
     }
 
     /** Starts a transition to move an immersive task out of immersive. */
@@ -123,7 +120,7 @@
         if (inProgress) {
             logV(
                 "Cannot start exit because transition(s) already in progress: %s",
-                getRunningTransitions(),
+                pendingImmersiveTransitions,
             )
             return
         }
@@ -134,13 +131,12 @@
             }
         logV("Moving task %d out of immersive mode, reason: %s", taskInfo.taskId, reason)
         val transition = transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ this)
-        state =
-            TransitionState(
-                transition = transition,
-                displayId = taskInfo.displayId,
-                taskId = taskInfo.taskId,
-                direction = Direction.EXIT,
-            )
+        addPendingImmersiveTransition(
+            taskId = taskInfo.taskId,
+            displayId = taskInfo.displayId,
+            direction = Direction.EXIT,
+            transition = transition,
+        )
     }
 
     /**
@@ -194,7 +190,13 @@
         return ExitResult.Exit(
             exitingTask = immersiveTask,
             runOnTransitionStart = { transition ->
-                addPendingImmersiveExit(immersiveTask, displayId, transition)
+                addPendingImmersiveTransition(
+                    taskId = immersiveTask,
+                    displayId = displayId,
+                    direction = Direction.EXIT,
+                    transition = transition,
+                    animate = false,
+                )
             },
         )
     }
@@ -220,10 +222,12 @@
             return ExitResult.Exit(
                 exitingTask = taskInfo.taskId,
                 runOnTransitionStart = { transition ->
-                    addPendingImmersiveExit(
+                    addPendingImmersiveTransition(
                         taskId = taskInfo.taskId,
                         displayId = taskInfo.displayId,
+                        direction = Direction.EXIT,
                         transition = transition,
+                        animate = false,
                     )
                 },
             )
@@ -233,14 +237,26 @@
 
     /** Whether the [change] in the [transition] is a known immersive change. */
     fun isImmersiveChange(transition: IBinder, change: TransitionInfo.Change): Boolean {
-        return pendingExternalExitTransitions.any {
+        return pendingImmersiveTransitions.any {
             it.transition == transition && it.taskId == change.taskInfo?.taskId
         }
     }
 
-    private fun addPendingImmersiveExit(taskId: Int, displayId: Int, transition: IBinder) {
-        pendingExternalExitTransitions.add(
-            ExternalPendingExit(taskId = taskId, displayId = displayId, transition = transition)
+    private fun addPendingImmersiveTransition(
+        taskId: Int,
+        displayId: Int,
+        direction: Direction,
+        transition: IBinder,
+        animate: Boolean = true,
+    ) {
+        pendingImmersiveTransitions.add(
+            PendingTransition(
+                taskId = taskId,
+                displayId = displayId,
+                direction = direction,
+                transition = transition,
+                animate = animate,
+            )
         )
     }
 
@@ -251,19 +267,17 @@
         finishTransaction: SurfaceControl.Transaction,
         finishCallback: Transitions.TransitionFinishCallback,
     ): Boolean {
-        val state = requireState()
-        check(state.transition == transition) {
-            "Transition $transition did not match expected state=$state"
-        }
+        val immersiveTransition = getImmersiveTransition(transition) ?: return false
+        if (!immersiveTransition.animate) return false
         logD("startAnimation transition=%s", transition)
         animateResize(
-            targetTaskId = state.taskId,
+            targetTaskId = immersiveTransition.taskId,
             info = info,
             startTransaction = startTransaction,
             finishTransaction = finishTransaction,
             finishCallback = {
                 finishCallback.onTransitionFinished(/* wct= */ null)
-                clearState()
+                pendingImmersiveTransitions.remove(immersiveTransition)
             },
         )
         return true
@@ -346,18 +360,6 @@
         request: TransitionRequestInfo,
     ): WindowContainerTransaction? = null
 
-    override fun onTransitionConsumed(
-        transition: IBinder,
-        aborted: Boolean,
-        finishTransaction: SurfaceControl.Transaction?,
-    ) {
-        val state = this.state ?: return
-        if (transition == state.transition && aborted) {
-            clearState()
-        }
-        super.onTransitionConsumed(transition, aborted, finishTransaction)
-    }
-
     /**
      * Called when any transition in the system is ready to play. This is needed to update the
      * repository state before window decorations are drawn (which happens immediately after
@@ -371,67 +373,42 @@
         finishTransaction: SurfaceControl.Transaction,
     ) {
         val desktopRepository: DesktopRepository = desktopUserRepositories.current
-        // Check if this is a pending external exit transition.
-        val pendingExit =
-            pendingExternalExitTransitions.firstOrNull { pendingExit ->
-                pendingExit.transition == transition
-            }
-        if (pendingExit != null) {
-            if (info.hasTaskChange(taskId = pendingExit.taskId)) {
-                if (desktopRepository.isTaskInFullImmersiveState(pendingExit.taskId)) {
-                    logV("Pending external exit for task#%d verified", pendingExit.taskId)
-                    desktopRepository.setTaskInFullImmersiveState(
-                        displayId = pendingExit.displayId,
-                        taskId = pendingExit.taskId,
-                        immersive = false,
-                    )
-                    if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) {
-                        desktopRepository.removeBoundsBeforeFullImmersive(pendingExit.taskId)
-                    }
-                }
-            }
-            return
-        }
+        val pendingTransition = getImmersiveTransition(transition)
 
-        // Check if this is a direct immersive enter/exit transition.
-        if (transition == state?.transition) {
-            val state = requireState()
-            val immersiveChange =
-                info.changes.firstOrNull { c -> c.taskInfo?.taskId == state.taskId }
+        if (pendingTransition != null) {
+            val taskId = pendingTransition.taskId
+            val immersiveChange = info.getTaskChange(taskId = taskId)
             if (immersiveChange == null) {
                 logV(
-                    "Direct move for task#%d in %s direction missing immersive change.",
-                    state.taskId,
-                    state.direction,
+                    "Transition for task#%d in %s direction missing immersive change.",
+                    taskId,
+                    pendingTransition.direction,
                 )
                 return
             }
-            val startBounds = immersiveChange.startAbsBounds
-            logV("Direct move for task#%d in %s direction verified", state.taskId, state.direction)
-
-            when (state.direction) {
-                Direction.ENTER -> {
-                    desktopRepository.setTaskInFullImmersiveState(
-                        displayId = state.displayId,
-                        taskId = state.taskId,
-                        immersive = true,
-                    )
-                    if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) {
-                        desktopRepository.saveBoundsBeforeFullImmersive(state.taskId, startBounds)
+            logV(
+                "Immersive transition for task#%d in %s direction verified",
+                taskId,
+                pendingTransition.direction,
+            )
+            desktopRepository.setTaskInFullImmersiveState(
+                displayId = pendingTransition.displayId,
+                taskId = taskId,
+                immersive = pendingTransition.direction == Direction.ENTER,
+            )
+            if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) {
+                when (pendingTransition.direction) {
+                    Direction.EXIT -> {
+                        desktopRepository.removeBoundsBeforeFullImmersive(taskId)
                     }
-                }
-                Direction.EXIT -> {
-                    desktopRepository.setTaskInFullImmersiveState(
-                        displayId = state.displayId,
-                        taskId = state.taskId,
-                        immersive = false,
-                    )
-                    if (Flags.enableRestoreToPreviousSizeFromDesktopImmersive()) {
-                        desktopRepository.removeBoundsBeforeFullImmersive(state.taskId)
+                    Direction.ENTER -> {
+                        desktopRepository.saveBoundsBeforeFullImmersive(
+                            taskId,
+                            immersiveChange.startAbsBounds,
+                        )
                     }
                 }
             }
-            return
         }
 
         // Check if this is an untracked exit transition, like display rotation.
@@ -450,35 +427,31 @@
     }
 
     override fun onTransitionMerged(merged: IBinder, playing: IBinder) {
-        val pendingExit =
-            pendingExternalExitTransitions.firstOrNull { pendingExit ->
-                pendingExit.transition == merged
+        val pendingTransition =
+            pendingImmersiveTransitions.firstOrNull { pendingTransition ->
+                pendingTransition.transition == merged
             }
-        if (pendingExit != null) {
+        if (pendingTransition != null) {
             logV(
-                "Pending exit transition %s for task#%s merged into %s",
+                "Pending transition %s for task#%s merged into %s",
                 merged,
-                pendingExit.taskId,
+                pendingTransition.taskId,
                 playing,
             )
-            pendingExit.transition = playing
+            pendingTransition.transition = playing
         }
     }
 
     override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
-        val pendingExit =
-            pendingExternalExitTransitions.firstOrNull { pendingExit ->
-                pendingExit.transition == transition
-            }
-        if (pendingExit != null) {
-            logV("Pending exit transition %s for task#%s finished", transition, pendingExit)
-            pendingExternalExitTransitions.remove(pendingExit)
+        val pendingTransition = getImmersiveTransition(transition)
+        if (pendingTransition != null) {
+            logV("Pending exit transition %s for task#%s finished", transition, pendingTransition)
+            pendingImmersiveTransitions.remove(pendingTransition)
         }
     }
 
-    private fun clearState() {
-        state = null
-    }
+    private fun getImmersiveTransition(transition: IBinder) =
+        pendingImmersiveTransitions.firstOrNull { it.transition == transition }
 
     private fun getExitDestinationBounds(taskInfo: RunningTaskInfo): Rect {
         val displayLayout =
@@ -496,24 +469,13 @@
         }
     }
 
-    private fun requireState(): TransitionState =
-        state ?: error("Expected non-null transition state")
-
-    private fun getRunningTransitions(): List<IBinder> {
-        val running = mutableListOf<IBinder>()
-        state?.let { running.add(it.transition) }
-        pendingExternalExitTransitions.forEach { running.add(it.transition) }
-        return running
-    }
-
-    private fun TransitionInfo.hasTaskChange(taskId: Int): Boolean =
-        changes.any { c -> c.taskInfo?.taskId == taskId }
+    private fun TransitionInfo.getTaskChange(taskId: Int): TransitionInfo.Change? =
+        changes.firstOrNull { c -> c.taskInfo?.taskId == taskId }
 
     private fun dump(pw: PrintWriter, prefix: String) {
         val innerPrefix = "$prefix  "
         pw.println("${prefix}DesktopImmersiveController")
-        pw.println(innerPrefix + "state=" + state)
-        pw.println(innerPrefix + "pendingExternalExitTransitions=" + pendingExternalExitTransitions)
+        pw.println(innerPrefix + "pendingImmersiveTransitions=" + pendingImmersiveTransitions)
     }
 
     /** The state of the currently running transition. */
@@ -526,12 +488,22 @@
     )
 
     /**
-     * Tracks state of a transition involving an immersive exit that is external to this class' own
-     * transitions. This usually means transitions that exit immersive mode as a side-effect and not
-     * the primary action (for example, minimizing the immersive task or launching a new task on top
-     * of the immersive task).
+     * Tracks state of a transition involving an immersive enter or exit. This includes both
+     * transitions that should and should not be animated by this handler.
+     *
+     * @param taskId of the task that should enter/exit immersive mode
+     * @param displayId of the display that should enter/exit immersive mode
+     * @param direction of the immersive transition
+     * @param transition that will apply this transaction
+     * @param animate whether transition should be animated by this handler
      */
-    data class ExternalPendingExit(val taskId: Int, val displayId: Int, var transition: IBinder)
+    data class PendingTransition(
+        val taskId: Int,
+        val displayId: Int,
+        val direction: Direction,
+        var transition: IBinder,
+        val animate: Boolean,
+    )
 
     /** The result of an external exit request. */
     sealed class ExitResult {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index 14623cf..606a729 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -235,8 +235,7 @@
 
 /** Returns true if task bound is equal to stable bounds else returns false. */
 fun isTaskBoundsEqual(taskBounds: Rect, stableBounds: Rect): Boolean {
-    return taskBounds.width() == stableBounds.width() &&
-        taskBounds.height() == stableBounds.height()
+    return taskBounds == stableBounds
 }
 
 /**
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 0bc7ca9..9a1abd5 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
@@ -383,12 +383,13 @@
         taskId: Int,
         wct: WindowContainerTransaction = WindowContainerTransaction(),
         transitionSource: DesktopModeTransitionSource,
+        remoteTransition: RemoteTransition? = null,
     ): Boolean {
         val runningTask = shellTaskOrganizer.getRunningTaskInfo(taskId)
         if (runningTask == null) {
-            return moveBackgroundTaskToDesktop(taskId, wct, transitionSource)
+            return moveBackgroundTaskToDesktop(taskId, wct, transitionSource, remoteTransition)
         }
-        moveRunningTaskToDesktop(runningTask, wct, transitionSource)
+        moveRunningTaskToDesktop(runningTask, wct, transitionSource, remoteTransition)
         return true
     }
 
@@ -396,6 +397,7 @@
         taskId: Int,
         wct: WindowContainerTransaction,
         transitionSource: DesktopModeTransitionSource,
+        remoteTransition: RemoteTransition? = null,
     ): Boolean {
         if (recentTasksController?.findTaskInBackground(taskId) == null) {
             logW("moveBackgroundTaskToDesktop taskId=%d not found", taskId)
@@ -418,8 +420,17 @@
                 .apply { launchWindowingMode = WINDOWING_MODE_FREEFORM }
                 .toBundle(),
         )
-        // TODO(343149901): Add DPI changes for task launch
-        val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
+
+        val transition: IBinder
+        if (remoteTransition != null) {
+            val transitionType = transitionType(remoteTransition)
+            val remoteTransitionHandler = OneShotRemoteHandler(mainExecutor, remoteTransition)
+            transition = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
+            remoteTransitionHandler.setTransition(transition)
+        } else {
+            // TODO(343149901): Add DPI changes for task launch
+            transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
+        }
         desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
             FREEFORM_ANIMATION_DURATION
         )
@@ -433,6 +444,7 @@
         task: RunningTaskInfo,
         wct: WindowContainerTransaction = WindowContainerTransaction(),
         transitionSource: DesktopModeTransitionSource,
+        remoteTransition: RemoteTransition? = null,
     ) {
         if (
             DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue() &&
@@ -450,12 +462,21 @@
                 excludeTaskId = task.taskId,
                 reason = DesktopImmersiveController.ExitReason.TASK_LAUNCH,
             )
+
         // Bring other apps to front first
         val taskIdToMinimize =
             bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId)
         addMoveToDesktopChanges(wct, task)
 
-        val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
+        val transition: IBinder
+        if (remoteTransition != null) {
+            val transitionType = transitionType(remoteTransition)
+            val remoteTransitionHandler = OneShotRemoteHandler(mainExecutor, remoteTransition)
+            transition = transitions.startTransition(transitionType, wct, remoteTransitionHandler)
+            remoteTransitionHandler.setTransition(transition)
+        } else {
+            transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct, transitionSource)
+        }
         desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
             FREEFORM_ANIMATION_DURATION
         )
@@ -2657,9 +2678,17 @@
             }
         }
 
-        override fun moveToDesktop(taskId: Int, transitionSource: DesktopModeTransitionSource) {
+        override fun moveToDesktop(
+            taskId: Int,
+            transitionSource: DesktopModeTransitionSource,
+            remoteTransition: RemoteTransition?,
+        ) {
             executeRemoteCallWithTaskPermission(controller, "moveTaskToDesktop") { c ->
-                c.moveTaskToDesktop(taskId, transitionSource = transitionSource)
+                c.moveTaskToDesktop(
+                    taskId,
+                    transitionSource = transitionSource,
+                    remoteTransition = remoteTransition,
+                )
             }
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index aac2361..fa383cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -53,7 +53,8 @@
     oneway void setTaskListener(IDesktopTaskListener listener);
 
     /** Move a task with given `taskId` to desktop */
-    void moveToDesktop(int taskId, in DesktopModeTransitionSource transitionSource);
+    void moveToDesktop(int taskId, in DesktopModeTransitionSource transitionSource,
+                        in @nullable RemoteTransition remoteTransition);
 
     /** Remove desktop on the given display */
     oneway void removeDesktop(int displayId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index ba724ed..e93ca9e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1969,32 +1969,32 @@
                 }
             }
 
-            int currentSnapPosition = mSplitLayout.calculateCurrentSnapPosition();
-
-            if (Flags.enableFlexibleTwoAppSplit()) {
-                // Split screen can be laid out in such a way that some of the apps are offscreen.
-                // For the purposes of passing SplitBounds up to launcher (for use in thumbnails
-                // etc.), we crop the bounds down to the screen size.
-                topLeftBounds.left =
-                        Math.max(topLeftBounds.left, 0);
-                topLeftBounds.top =
-                        Math.max(topLeftBounds.top, 0);
-                bottomRightBounds.right =
-                        Math.min(bottomRightBounds.right, mSplitLayout.getDisplayWidth());
-                bottomRightBounds.top =
-                        Math.min(bottomRightBounds.top, mSplitLayout.getDisplayHeight());
-
-                // TODO (b/349828130): Can change to getState() fully after brief soak time.
-                if (mSplitState.get() != currentSnapPosition) {
-                    Log.wtf(TAG, "SplitState is " + mSplitState.get()
-                            + ", expected " + currentSnapPosition);
-                    currentSnapPosition = mSplitState.get();
-                }
-            }
-
-            SplitBounds splitBounds = new SplitBounds(topLeftBounds, bottomRightBounds,
-                    leftTopTaskId, rightBottomTaskId, currentSnapPosition);
+            // If all stages are filled, create new SplitBounds and update Recents.
             if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
+                int currentSnapPosition = mSplitLayout.calculateCurrentSnapPosition();
+                if (Flags.enableFlexibleTwoAppSplit()) {
+                    // Split screen can be laid out in such a way that some of the apps are
+                    // offscreen. For the purposes of passing SplitBounds up to launcher (for use in
+                    // thumbnails etc.), we crop the bounds down to the screen size.
+                    topLeftBounds.left =
+                            Math.max(topLeftBounds.left, 0);
+                    topLeftBounds.top =
+                            Math.max(topLeftBounds.top, 0);
+                    bottomRightBounds.right =
+                            Math.min(bottomRightBounds.right, mSplitLayout.getDisplayWidth());
+                    bottomRightBounds.top =
+                            Math.min(bottomRightBounds.top, mSplitLayout.getDisplayHeight());
+
+                    // TODO (b/349828130): Can change to getState() fully after brief soak time.
+                    if (mSplitState.get() != currentSnapPosition) {
+                        Log.wtf(TAG, "SplitState is " + mSplitState.get()
+                                + ", expected " + currentSnapPosition);
+                        currentSnapPosition = mSplitState.get();
+                    }
+                }
+                SplitBounds splitBounds = new SplitBounds(topLeftBounds, bottomRightBounds,
+                        leftTopTaskId, rightBottomTaskId, currentSnapPosition);
+
                 // Update the pair for the top tasks
                 boolean added = recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId,
                         splitBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt
index 3fa8df4..a921004 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageOrderOperator.kt
@@ -95,13 +95,16 @@
      */
     fun onEnteringSplit(@SnapPosition goingToLayout: Int) {
         if (goingToLayout == currentLayout) {
-            // Add protolog here. Return for now, but maybe we want to handle swap case, TBD
+            ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                "Entering Split requested same layout split is in: %d", goingToLayout)
             return
         }
         val freeStages: List<StageTaskListener> =
             allStages.filterNot { activeStages.contains(it) }
         when(goingToLayout) {
-            SplitScreenConstants.SNAP_TO_2_50_50 -> {
+            SplitScreenConstants.SNAP_TO_2_50_50,
+            SplitScreenConstants.SNAP_TO_2_33_66,
+            SplitScreenConstants.SNAP_TO_2_66_33 -> {
                 if (activeStages.size < 2) {
                     // take from allStages and add into activeStages
                     for (i in 0 until (2 - activeStages.size)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 611f3e0..a7d6301 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -1076,9 +1076,11 @@
             @Nullable TransitionHandler skip
     ) {
         for (int i = mHandlers.size() - 1; i >= 0; --i) {
-            if (mHandlers.get(i) == skip) continue;
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try handler %s",
-                    mHandlers.get(i));
+            if (mHandlers.get(i) == skip) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " skip handler %s",
+                        mHandlers.get(i));
+                continue;
+            }
             boolean consumed = mHandlers.get(i).startAnimation(transition, info, startT, finishT,
                     finishCB);
             if (consumed) {
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 0f5813c..aea4bda 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
@@ -717,7 +717,8 @@
         // App sometimes draws before the insets from WindowDecoration#relayout have
         // been added, so they must be added here
         decoration.addCaptionInset(wct);
-        mDesktopTasksController.moveTaskToDesktop(taskId, wct, source);
+        mDesktopTasksController.moveTaskToDesktop(taskId, wct, source,
+                /* remoteTransition= */ null);
         decoration.closeHandleMenu();
 
         if (source == DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 6562f38..01319fb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -98,6 +98,7 @@
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.desktopmode.CaptionState;
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
+import com.android.wm.shell.desktopmode.DesktopModeUtils;
 import com.android.wm.shell.desktopmode.DesktopUserRepositories;
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
@@ -522,7 +523,7 @@
         } else {
             mWindowDecorViewHolder.bindData(new AppHeaderViewHolder.HeaderData(
                     mTaskInfo,
-                    TaskInfoKt.getRequestingImmersive(mTaskInfo),
+                    DesktopModeUtils.isTaskMaximized(mTaskInfo, mDisplayController),
                     inFullImmersive,
                     hasGlobalFocus,
                     /* maximizeHoverEnabled= */ canOpenMaximizeMenu(
@@ -556,10 +557,10 @@
     @Nullable
     private Intent getBrowserLink() {
         final Uri browserLink;
-        if (isCapturedLinkAvailable()) {
-            browserLink = mCapturedLink.mUri;
-        } else if (mWebUri != null) {
+        if (mWebUri != null) {
             browserLink = mWebUri;
+        } else if (isCapturedLinkAvailable()) {
+            browserLink = mCapturedLink.mUri;
         } else {
             browserLink = mGenericLink;
         }
@@ -1316,7 +1317,7 @@
      */
     @VisibleForTesting
     void onAssistContentReceived(@Nullable AssistContent assistContent) {
-        mWebUri = assistContent == null ? null : assistContent.getWebUri();
+        mWebUri = assistContent == null ? null : AppToWebUtils.getSessionWebUri(assistContent);
         loadAppInfoIfNeeded();
         updateGenericLink();
         final boolean supportsMultiInstance = mMultiInstanceHelper
@@ -1705,7 +1706,7 @@
                         .isTaskInFullImmersiveState(mTaskInfo.taskId);
         asAppHeader(mWindowDecorViewHolder).bindData(new AppHeaderViewHolder.HeaderData(
                 mTaskInfo,
-                TaskInfoKt.getRequestingImmersive(mTaskInfo),
+                DesktopModeUtils.isTaskMaximized(mTaskInfo, mDisplayController),
                 inFullImmersive,
                 isFocused(),
                 /* maximizeHoverEnabled= */ canOpenMaximizeMenu(animatingTaskResizeOrReposition)));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
index a474160..f3a8b20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt
@@ -79,7 +79,7 @@
 
     data class HeaderData(
         val taskInfo: RunningTaskInfo,
-        val isRequestingImmersive: Boolean,
+        val isTaskMaximized: Boolean,
         val inFullImmersiveState: Boolean,
         val hasGlobalFocus: Boolean,
         val enableMaximizeLongClick: Boolean,
@@ -163,7 +163,7 @@
     override fun bindData(data: HeaderData) {
         bindData(
             data.taskInfo,
-            data.isRequestingImmersive,
+            data.isTaskMaximized,
             data.inFullImmersiveState,
             data.hasGlobalFocus,
             data.enableMaximizeLongClick
@@ -172,7 +172,7 @@
 
     private fun bindData(
         taskInfo: RunningTaskInfo,
-        isRequestingImmersive: Boolean,
+        isTaskMaximized: Boolean,
         inFullImmersiveState: Boolean,
         hasGlobalFocus: Boolean,
         enableMaximizeLongClick: Boolean,
@@ -180,7 +180,7 @@
         if (DesktopModeFlags.ENABLE_THEMED_APP_HEADERS.isTrue()) {
             bindDataWithThemedHeaders(
                 taskInfo,
-                isRequestingImmersive,
+                isTaskMaximized,
                 inFullImmersiveState,
                 hasGlobalFocus,
                 enableMaximizeLongClick,
@@ -225,7 +225,7 @@
 
     private fun bindDataWithThemedHeaders(
         taskInfo: RunningTaskInfo,
-        requestingImmersive: Boolean,
+        isTaskMaximized: Boolean,
         inFullImmersiveState: Boolean,
         hasGlobalFocus: Boolean,
         enableMaximizeLongClick: Boolean,
@@ -283,7 +283,7 @@
                     drawableInsets = maximizeDrawableInsets
                 )
             )
-            setIcon(getMaximizeButtonIcon(requestingImmersive, inFullImmersiveState))
+            setIcon(getMaximizeButtonIcon(isTaskMaximized, inFullImmersiveState))
         }
         // Close button.
         closeWindowButton.apply {
@@ -358,34 +358,19 @@
 
     @DrawableRes
     private fun getMaximizeButtonIcon(
-        requestingImmersive: Boolean,
+        isTaskMaximized: Boolean,
         inFullImmersiveState: Boolean
     ): Int = when {
-        shouldShowEnterFullImmersiveIcon(requestingImmersive, inFullImmersiveState) -> {
-            R.drawable.decor_desktop_mode_immersive_button_dark
-        }
-        shouldShowExitFullImmersiveIcon(requestingImmersive, inFullImmersiveState) -> {
-            R.drawable.decor_desktop_mode_immersive_exit_button_dark
+        shouldShowExitFullImmersiveOrMaximizeIcon(isTaskMaximized, inFullImmersiveState) -> {
+            R.drawable.decor_desktop_mode_immersive_or_maximize_exit_button_dark
         }
         else -> R.drawable.decor_desktop_mode_maximize_button_dark
     }
 
-    private fun shouldShowEnterFullImmersiveIcon(
-        requestingImmersive: Boolean,
+    private fun shouldShowExitFullImmersiveOrMaximizeIcon(
+        isTaskMaximized: Boolean,
         inFullImmersiveState: Boolean
-    ): Boolean = Flags.enableFullyImmersiveInDesktop()
-            && requestingImmersive && !inFullImmersiveState
-
-    private fun shouldShowExitFullImmersiveIcon(
-        requestingImmersive: Boolean,
-        inFullImmersiveState: Boolean
-    ): Boolean = isInFullImmersiveStateAndRequesting(requestingImmersive, inFullImmersiveState)
-
-    private fun isInFullImmersiveStateAndRequesting(
-        requestingImmersive: Boolean,
-        inFullImmersiveState: Boolean
-    ): Boolean = Flags.enableFullyImmersiveInDesktop()
-            && requestingImmersive && inFullImmersiveState
+    ): Boolean = (Flags.enableFullyImmersiveInDesktop() && inFullImmersiveState) || isTaskMaximized
 
     private fun getHeaderStyle(header: Header): HeaderStyle {
         return HeaderStyle(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index a2afd2c..47ee7bb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -718,7 +718,7 @@
         tInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open);
         callback = mock(Transitions.TransitionFinishCallback.class);
         mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback);
-        verify(mBackTransitionHandler).handlePrepareTransition(
+        verify(mBackTransitionHandler).handlePrepareTransition(eq(mockBinder),
                 eq(tInfo), eq(st), eq(ft), eq(callback));
         mBackTransitionHandler.mCloseTransitionRequested = true;
         TransitionInfo tInfo2 = createTransitionInfo(TRANSIT_CLOSE, close);
@@ -750,7 +750,7 @@
                 null /* remoteTransition */);
         mBackTransitionHandler.handleRequest(mockBinder, requestInfo);
         mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback);
-        verify(mBackTransitionHandler).handlePrepareTransition(
+        verify(mBackTransitionHandler).handlePrepareTransition(eq(mockBinder),
                 eq(tInfo), eq(st), eq(ft), eq(callback));
 
         mBackTransitionHandler.onAnimationFinished();
@@ -801,7 +801,7 @@
         canHandle = mBackTransitionHandler.startAnimation(mockBinder,
                 prepareInfo, st, ft, callback2);
         assertTrue("Handle prepare transition" , canHandle);
-        verify(mBackTransitionHandler).handlePrepareTransition(
+        verify(mBackTransitionHandler).handlePrepareTransition(eq(mockBinder),
                 eq(prepareInfo), eq(st), eq(ft), eq(callback2));
         final TransitionInfo closeInfo = createTransitionInfo(TRANSIT_CLOSE, close);
         Transitions.TransitionFinishCallback mergeCallback =
@@ -819,7 +819,7 @@
         canHandle = mBackTransitionHandler.startAnimation(
                 mockBinder, prepareInfo, st, ft, callback3);
         assertTrue("Handle prepare transition" , canHandle);
-        verify(mBackTransitionHandler).handlePrepareTransition(
+        verify(mBackTransitionHandler).handlePrepareTransition(eq(mockBinder),
                 eq(prepareInfo), eq(st), eq(ft), eq(callback3));
         final TransitionInfo.Change open2 = createAppChange(
                 openTaskId2, TRANSIT_OPEN, FLAG_MOVED_TO_TOP);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/OWNERS b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/OWNERS
index 5b05af9..3a017f3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/OWNERS
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/OWNERS
@@ -1,2 +1,8 @@
 # Bug component: 970984
-# includes OWNERS from parent directories
\ No newline at end of file
+# includes OWNERS from parent directories
+
+mariiasand@google.com
+mcarli@google.com
+minagranic@google.com
+gracielawputri@google.com
+eevlachavas@google.com
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt
index 95a0c82..88cc981 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxControllerRobotTest.kt
@@ -16,10 +16,8 @@
 
 package com.android.wm.shell.compatui.letterbox
 
-import android.content.Context
 import android.graphics.Rect
 import android.view.SurfaceControl
-import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
 import com.android.wm.shell.compatui.letterbox.LetterboxMatchers.asAnyMode
 import org.mockito.kotlin.any
 import org.mockito.kotlin.clearInvocations
@@ -31,10 +29,7 @@
 /**
  * Robot to test [LetterboxController] implementations.
  */
-open class LetterboxControllerRobotTest(
-    ctx: Context,
-    controllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController
-) {
+abstract class LetterboxControllerRobotTest {
 
     companion object {
         @JvmStatic
@@ -44,21 +39,21 @@
         private val TASK_ID = 20
     }
 
-    private val letterboxConfiguration: LetterboxConfiguration
-    private val surfaceBuilder: LetterboxSurfaceBuilder
-    private val letterboxController: LetterboxController
-    private val transaction: SurfaceControl.Transaction
-    private val parentLeash: SurfaceControl
+    lateinit var letterboxController: LetterboxController
+    val transaction: SurfaceControl.Transaction
+    val parentLeash: SurfaceControl
 
     init {
-        letterboxConfiguration = LetterboxConfiguration(ctx)
-        surfaceBuilder = LetterboxSurfaceBuilder(letterboxConfiguration)
-        letterboxController = controllerBuilder(surfaceBuilder)
         transaction = getTransactionMock()
         parentLeash = mock<SurfaceControl>()
-        spyOn(surfaceBuilder)
     }
 
+    fun initController() {
+        letterboxController = buildController()
+    }
+
+    abstract fun buildController(): LetterboxController
+
     fun sendCreateSurfaceRequest(
         displayId: Int = DISPLAY_ID,
         taskId: Int = TASK_ID
@@ -102,16 +97,6 @@
         letterboxController.dump()
     }
 
-    fun checkSurfaceBuilderInvoked(times: Int = 1, name: String = "", callSite: String = "") {
-        verify(surfaceBuilder, times(times)).createSurface(
-            eq(transaction),
-            eq(parentLeash),
-            name.asAnyMode(),
-            callSite.asAnyMode(),
-            any()
-        )
-    }
-
     fun checkTransactionRemovedInvoked(times: Int = 1) {
         verify(transaction, times(times)).remove(any())
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt
index 6675112..dd4cb11 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/LetterboxUtilsTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.compatui.letterbox
 
-import android.content.Context
 import android.graphics.Rect
 import android.testing.AndroidTestingRunner
 import android.view.SurfaceControl
@@ -44,24 +43,14 @@
 @SmallTest
 class LetterboxUtilsTest : ShellTestCase() {
 
-    val firstLetterboxController = mock<LetterboxController>()
-    val secondLetterboxController = mock<LetterboxController>()
-    val thirdLetterboxController = mock<LetterboxController>()
-
-    private val letterboxControllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController =
-        { _ ->
-            firstLetterboxController.append(secondLetterboxController)
-                .append(thirdLetterboxController)
-        }
-
     @Test
     fun `Appended LetterboxController invoked creation on all the controllers`() {
         runTestScenario { r ->
             r.sendCreateSurfaceRequest()
 
-            r.verifyCreateSurfaceInvokedWithRequest(target = firstLetterboxController)
-            r.verifyCreateSurfaceInvokedWithRequest(target = secondLetterboxController)
-            r.verifyCreateSurfaceInvokedWithRequest(target = thirdLetterboxController)
+            r.verifyCreateSurfaceInvokedWithRequest(target = r.firstLetterboxController)
+            r.verifyCreateSurfaceInvokedWithRequest(target = r.secondLetterboxController)
+            r.verifyCreateSurfaceInvokedWithRequest(target = r.thirdLetterboxController)
         }
     }
 
@@ -69,9 +58,9 @@
     fun `Appended LetterboxController invoked destroy on all the controllers`() {
         runTestScenario { r ->
             r.sendDestroySurfaceRequest()
-            r.verifyDestroySurfaceInvokedWithRequest(target = firstLetterboxController)
-            r.verifyDestroySurfaceInvokedWithRequest(target = secondLetterboxController)
-            r.verifyDestroySurfaceInvokedWithRequest(target = thirdLetterboxController)
+            r.verifyDestroySurfaceInvokedWithRequest(target = r.firstLetterboxController)
+            r.verifyDestroySurfaceInvokedWithRequest(target = r.secondLetterboxController)
+            r.verifyDestroySurfaceInvokedWithRequest(target = r.thirdLetterboxController)
         }
     }
 
@@ -79,9 +68,9 @@
     fun `Appended LetterboxController invoked update visibility on all the controllers`() {
         runTestScenario { r ->
             r.sendUpdateSurfaceVisibilityRequest(visible = true)
-            r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = firstLetterboxController)
-            r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = secondLetterboxController)
-            r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = thirdLetterboxController)
+            r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = r.firstLetterboxController)
+            r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = r.secondLetterboxController)
+            r.verifyUpdateVisibilitySurfaceInvokedWithRequest(target = r.thirdLetterboxController)
         }
     }
 
@@ -89,9 +78,9 @@
     fun `Appended LetterboxController invoked update bounds on all the controllers`() {
         runTestScenario { r ->
             r.sendUpdateSurfaceBoundsRequest(taskBounds = Rect(), activityBounds = Rect())
-            r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = firstLetterboxController)
-            r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = secondLetterboxController)
-            r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = thirdLetterboxController)
+            r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = r.firstLetterboxController)
+            r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = r.secondLetterboxController)
+            r.verifyUpdateSurfaceBoundsInvokedWithRequest(target = r.thirdLetterboxController)
         }
     }
 
@@ -99,9 +88,9 @@
     fun `Appended LetterboxController invoked update dump on all the controllers`() {
         runTestScenario { r ->
             r.invokeDump()
-            r.verifyDumpInvoked(target = firstLetterboxController)
-            r.verifyDumpInvoked(target = secondLetterboxController)
-            r.verifyDumpInvoked(target = thirdLetterboxController)
+            r.verifyDumpInvoked(target = r.firstLetterboxController)
+            r.verifyDumpInvoked(target = r.secondLetterboxController)
+            r.verifyDumpInvoked(target = r.thirdLetterboxController)
         }
     }
 
@@ -138,21 +127,20 @@
      * Runs a test scenario providing a Robot.
      */
     fun runTestScenario(consumer: Consumer<AppendLetterboxControllerRobotTest>) {
-        val robot = AppendLetterboxControllerRobotTest(mContext, letterboxControllerBuilder)
-        consumer.accept(robot)
+        consumer.accept(AppendLetterboxControllerRobotTest().apply { initController() })
     }
 
-    class AppendLetterboxControllerRobotTest(
-        ctx: Context,
-        builder: (LetterboxSurfaceBuilder) -> LetterboxController
-    ) : LetterboxControllerRobotTest(ctx, builder) {
+    class AppendLetterboxControllerRobotTest : LetterboxControllerRobotTest() {
+
+        val firstLetterboxController = mock<LetterboxController>()
+        val secondLetterboxController = mock<LetterboxController>()
+        val thirdLetterboxController = mock<LetterboxController>()
 
         private var testableMap = mutableMapOf<Int, Int>()
         private var onItemState: Int? = null
         private var onMissingStateKey: Int? = null
         private var onMissingStateMap: MutableMap<Int, Int>? = null
 
-        private val transaction = getTransactionMock()
         private val surface = SurfaceControl()
 
         fun verifyCreateSurfaceInvokedWithRequest(
@@ -230,5 +218,9 @@
         fun verifySetWindowCrop(expectedWidth: Int, expectedHeight: Int) {
             verify(transaction).setWindowCrop(surface, expectedWidth, expectedHeight)
         }
+
+        override fun buildController(): LetterboxController =
+            firstLetterboxController.append(secondLetterboxController)
+                .append(thirdLetterboxController)
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt
index e6bff4c..3b72ff1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MixedLetterboxControllerTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.compatui.letterbox
 
-import android.content.Context
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.ShellTestCase
@@ -64,65 +63,48 @@
      * Runs a test scenario providing a Robot.
      */
     fun runTestScenario(consumer: Consumer<MixedLetterboxControllerRobotTest>) {
-        val robot = MixedLetterboxControllerRobotTest(mContext, ObjectToTestHolder())
-        consumer.accept(robot)
+        consumer.accept(MixedLetterboxControllerRobotTest().apply { initController() })
     }
 
-    class MixedLetterboxControllerRobotTest(
-        ctx: Context,
-        private val objectToTestHolder: ObjectToTestHolder
-    ) : LetterboxControllerRobotTest(ctx, objectToTestHolder.controllerBuilder) {
+    class MixedLetterboxControllerRobotTest : LetterboxControllerRobotTest() {
+        val singleLetterboxController: SingleSurfaceLetterboxController =
+            mock<SingleSurfaceLetterboxController>()
+        val multipleLetterboxController: MultiSurfaceLetterboxController =
+            mock<MultiSurfaceLetterboxController>()
+        val controllerStrategy: LetterboxControllerStrategy = mock<LetterboxControllerStrategy>()
 
         fun configureStrategyFor(letterboxMode: LetterboxMode) {
-            doReturn(letterboxMode).`when`(objectToTestHolder.controllerStrategy)
-                .getLetterboxImplementationMode()
+            doReturn(letterboxMode).`when`(controllerStrategy).getLetterboxImplementationMode()
         }
 
         fun checkCreateInvokedOnSingleController(times: Int = 1) {
-            verify(
-                objectToTestHolder.singleLetterboxController,
-                times(times)
-            ).createLetterboxSurface(any(), any(), any())
+            verify(singleLetterboxController, times(times)).createLetterboxSurface(
+                any(),
+                any(),
+                any()
+            )
         }
 
         fun checkCreateInvokedOnMultiController(times: Int = 1) {
-            verify(
-                objectToTestHolder.multipleLetterboxController,
-                times(times)
-            ).createLetterboxSurface(any(), any(), any())
+            verify(multipleLetterboxController, times(times)).createLetterboxSurface(
+                any(),
+                any(),
+                any()
+            )
         }
 
         fun checkDestroyInvokedOnSingleController(times: Int = 1) {
-            verify(
-                objectToTestHolder.singleLetterboxController,
-                times(times)
-            ).destroyLetterboxSurface(any(), any())
+            verify(singleLetterboxController, times(times)).destroyLetterboxSurface(any(), any())
         }
 
         fun checkDestroyInvokedOnMultiController(times: Int = 1) {
-            verify(
-                objectToTestHolder.multipleLetterboxController,
-                times(times)
-            ).destroyLetterboxSurface(any(), any())
+            verify(multipleLetterboxController, times(times)).destroyLetterboxSurface(any(), any())
         }
-    }
 
-    data class ObjectToTestHolder(
-        val singleLetterboxController: SingleSurfaceLetterboxController =
-        mock<SingleSurfaceLetterboxController>(),
-        val multipleLetterboxController: MultiSurfaceLetterboxController =
-        mock<MultiSurfaceLetterboxController>(),
-        val controllerStrategy: LetterboxControllerStrategy = mock<LetterboxControllerStrategy>()
-    ) {
-
-        private val mixedController =
-            MixedLetterboxController(
-                singleLetterboxController,
-                multipleLetterboxController,
-                controllerStrategy
-            )
-
-        val controllerBuilder: (LetterboxSurfaceBuilder) -> LetterboxController =
-            { _ -> mixedController }
+        override fun buildController(): LetterboxController = MixedLetterboxController(
+            singleLetterboxController,
+            multipleLetterboxController,
+            controllerStrategy
+        )
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt
index 295d4ed..3fd837d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/MultiSurfaceLetterboxControllerTest.kt
@@ -16,13 +16,20 @@
 
 package com.android.wm.shell.compatui.letterbox
 
+import android.content.Context
 import android.graphics.Rect
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
 import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.compatui.letterbox.LetterboxMatchers.asAnyMode
 import java.util.function.Consumer
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
 
 /**
  * Tests for [MultiSurfaceLetterboxController].
@@ -147,9 +154,33 @@
     /**
      * Runs a test scenario providing a Robot.
      */
-    fun runTestScenario(consumer: Consumer<LetterboxControllerRobotTest>) {
-        val robot =
-            LetterboxControllerRobotTest(mContext, { sb -> MultiSurfaceLetterboxController(sb) })
-        consumer.accept(robot)
+    fun runTestScenario(consumer: Consumer<MultiLetterboxControllerRobotTest>) {
+        consumer.accept(MultiLetterboxControllerRobotTest(mContext).apply { initController() })
+    }
+
+    class MultiLetterboxControllerRobotTest(context: Context) :
+        LetterboxControllerRobotTest() {
+
+        private val letterboxConfiguration: LetterboxConfiguration
+        private val surfaceBuilder: LetterboxSurfaceBuilder
+
+        init {
+            letterboxConfiguration = LetterboxConfiguration(context)
+            surfaceBuilder = LetterboxSurfaceBuilder(letterboxConfiguration)
+            spyOn(surfaceBuilder)
+        }
+
+        override fun buildController(): LetterboxController =
+            MultiSurfaceLetterboxController(surfaceBuilder)
+
+        fun checkSurfaceBuilderInvoked(times: Int = 1, name: String = "", callSite: String = "") {
+            verify(surfaceBuilder, times(times)).createSurface(
+                eq(transaction),
+                eq(parentLeash),
+                name.asAnyMode(),
+                callSite.asAnyMode(),
+                any()
+            )
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt
index 125e700..e6ffe98 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterbox/SingleSurfaceLetterboxControllerTest.kt
@@ -16,13 +16,20 @@
 
 package com.android.wm.shell.compatui.letterbox
 
+import android.content.Context
 import android.graphics.Rect
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
 import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.compatui.letterbox.LetterboxMatchers.asAnyMode
 import java.util.function.Consumer
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
 
 /**
  * Tests for [SingleSurfaceLetterboxController].
@@ -120,9 +127,33 @@
     /**
      * Runs a test scenario providing a Robot.
      */
-    fun runTestScenario(consumer: Consumer<LetterboxControllerRobotTest>) {
-        val robot =
-            LetterboxControllerRobotTest(mContext, { sb -> SingleSurfaceLetterboxController(sb) })
-        consumer.accept(robot)
+    fun runTestScenario(consumer: Consumer<SingleLetterboxControllerRobotTest>) {
+        consumer.accept(SingleLetterboxControllerRobotTest(mContext).apply { initController() })
+    }
+
+    class SingleLetterboxControllerRobotTest(context: Context) :
+        LetterboxControllerRobotTest() {
+
+        private val letterboxConfiguration: LetterboxConfiguration
+        private val surfaceBuilder: LetterboxSurfaceBuilder
+
+        init {
+            letterboxConfiguration = LetterboxConfiguration(context)
+            surfaceBuilder = LetterboxSurfaceBuilder(letterboxConfiguration)
+            spyOn(surfaceBuilder)
+        }
+
+        override fun buildController(): LetterboxController =
+            SingleSurfaceLetterboxController(surfaceBuilder)
+
+        fun checkSurfaceBuilderInvoked(times: Int = 1, name: String = "", callSite: String = "") {
+            verify(surfaceBuilder, times(times)).createSurface(
+                eq(transaction),
+                eq(parentLeash),
+                name.asAnyMode(),
+                callSite.asAnyMode(),
+                any()
+            )
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
index db4c746..447da87 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
@@ -42,6 +42,7 @@
 import com.android.wm.shell.TestShellExecutor
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.desktopmode.DesktopImmersiveController.Direction
 import com.android.wm.shell.desktopmode.DesktopImmersiveController.ExitReason.USER_INTERACTION
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
 import com.android.wm.shell.sysui.ShellInit
@@ -95,6 +96,8 @@
         whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { invocation ->
             (invocation.getArgument(0) as Rect).set(STABLE_BOUNDS)
         }
+        whenever(mockDisplayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
+        whenever(mockDisplayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
         controller = DesktopImmersiveController(
             shellInit = mock(),
             transitions = mockTransitions,
@@ -277,10 +280,12 @@
 
         controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION)
 
-        assertThat(controller.pendingExternalExitTransitions.any { exit ->
-            exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
-                    && exit.taskId == task.taskId
-        }).isTrue()
+        assertTransitionPending(
+            transition = transition,
+            taskId = task.taskId,
+            direction = Direction.EXIT,
+            animate = false
+        )
     }
 
     @Test
@@ -298,10 +303,12 @@
 
         controller.exitImmersiveIfApplicable(transition, wct, DEFAULT_DISPLAY, USER_INTERACTION)
 
-        assertThat(controller.pendingExternalExitTransitions.any { exit ->
-            exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
-                    && exit.taskId == task.taskId
-        }).isFalse()
+        assertTransitionNotPending(
+            transition = transition,
+            taskId = task.taskId,
+            direction = Direction.EXIT,
+            animate = false
+        )
     }
 
     @Test
@@ -360,10 +367,12 @@
             reason = USER_INTERACTION,
         ).asExit()?.runOnTransitionStart?.invoke(transition)
 
-        assertThat(controller.pendingExternalExitTransitions.any { exit ->
-            exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
-                    && exit.taskId == task.taskId
-        }).isFalse()
+        assertTransitionNotPending(
+            transition = transition,
+            taskId = task.taskId,
+            animate = false,
+            direction = Direction.EXIT
+        )
     }
 
     @Test
@@ -416,10 +425,12 @@
         controller.exitImmersiveIfApplicable(wct, task, USER_INTERACTION)
             .asExit()?.runOnTransitionStart?.invoke(transition)
 
-        assertThat(controller.pendingExternalExitTransitions.any { exit ->
-            exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
-                    && exit.taskId == task.taskId
-        }).isTrue()
+        assertTransitionPending(
+            transition = transition,
+            taskId = task.taskId,
+            direction = Direction.EXIT,
+            animate = false
+        )
     }
 
     @Test
@@ -481,10 +492,12 @@
         )
         controller.onTransitionFinished(transition, aborted = false)
 
-        assertThat(controller.pendingExternalExitTransitions.any { exit ->
-            exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
-                    && exit.taskId == task.taskId
-        }).isFalse()
+        assertTransitionNotPending(
+            transition = transition,
+            taskId = task.taskId,
+            direction = Direction.EXIT,
+            animate = false
+        )
     }
 
     @Test
@@ -513,14 +526,18 @@
         controller.onTransitionMerged(transition, mergedToTransition)
         controller.onTransitionFinished(mergedToTransition, aborted = false)
 
-        assertThat(controller.pendingExternalExitTransitions.any { exit ->
-            exit.transition == transition && exit.displayId == DEFAULT_DISPLAY
-                    && exit.taskId == task.taskId
-        }).isFalse()
-        assertThat(controller.pendingExternalExitTransitions.any { exit ->
-            exit.transition == mergedToTransition && exit.displayId == DEFAULT_DISPLAY
-                    && exit.taskId == task.taskId
-        }).isFalse()
+        assertTransitionNotPending(
+            transition = transition,
+            taskId = task.taskId,
+            animate = false,
+            direction = Direction.EXIT
+        )
+        assertTransitionNotPending(
+            transition = mergedToTransition,
+            taskId = task.taskId,
+            animate = false,
+            direction = Direction.EXIT
+        )
     }
 
     @Test
@@ -686,7 +703,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
-    fun externalAnimateResizeChange_doesNotCleanUpPendingTransitionState() {
+    fun externalAnimateResizeChange_doesNotRemovePendingTransition() {
         val task = createFreeformTask()
         val mockBinder = mock(IBinder::class.java)
         whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)))
@@ -709,12 +726,16 @@
         )
         animatorTestRule.advanceTimeBy(DesktopImmersiveController.FULL_IMMERSIVE_ANIM_DURATION_MS)
 
-        assertThat(controller.state).isNotNull()
+        assertTransitionPending(
+            transition = mockBinder,
+            taskId = task.taskId,
+            direction = Direction.EXIT
+        )
     }
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
-    fun startAnimation_missingChange_clearsState() {
+    fun startAnimation_missingChange_removesPendingTransition() {
         val task = createFreeformTask()
         val mockBinder = mock(IBinder::class.java)
         whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)))
@@ -735,7 +756,42 @@
             finishCallback = {}
         )
 
-        assertThat(controller.state).isNull()
+        assertTransitionNotPending(
+            transition = mockBinder,
+            taskId = task.taskId,
+            direction = Direction.ENTER
+        )
+    }
+
+    private fun assertTransitionPending(
+        transition: IBinder,
+        taskId: Int,
+        direction: Direction,
+        animate: Boolean = true,
+        displayId: Int = DEFAULT_DISPLAY
+    ) {
+        assertThat(controller.pendingImmersiveTransitions.any { pendingTransition ->
+            pendingTransition.transition == transition
+                    && pendingTransition.displayId == displayId
+                    && pendingTransition.taskId == taskId
+                    && pendingTransition.animate == animate
+                    && pendingTransition.direction == direction
+        }).isTrue()
+    }
+
+    private fun assertTransitionNotPending(
+        transition: IBinder,
+        taskId: Int,
+        direction: Direction,
+        animate: Boolean = true,
+        displayId: Int = DEFAULT_DISPLAY
+    ) {
+        assertThat(controller.pendingImmersiveTransitions.any { pendingTransition ->
+            pendingTransition.transition == transition
+                    && pendingTransition.displayId == displayId
+                    && pendingTransition.taskId == taskId
+                    && pendingTransition.direction == direction
+        }).isFalse()
     }
 
     private fun createTransitionInfo(
@@ -768,5 +824,6 @@
 
     companion object {
         private val STABLE_BOUNDS = Rect(0, 100, 2000, 1900)
+        private val DISPLAY_BOUNDS = Rect(0, 0, 2000, 2000)
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 7c9494c..33c1451 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -1217,14 +1217,40 @@
   }
 
   @Test
-  fun moveRunningTaskToDesktop_deviceSupported_taskIsMovedToDesktop() {
-    val task = setUpFullscreenTask()
+  @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
+  fun moveBackgroundTaskToDesktop_remoteTransition_usesOneShotHandler() {
+    val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+    whenever(
+      transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture())
+    ).thenReturn(Binder())
 
-    controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+    val task = createTaskInfo(1)
+    whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
+    whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
+    controller.moveTaskToDesktop(
+      taskId = task.taskId,
+      transitionSource = UNKNOWN,
+      remoteTransition = RemoteTransition(spy(TestRemoteTransition())))
 
-    val wct = getLatestEnterDesktopWct()
-    assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
     verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+    assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
+  }
+
+
+  @Test
+  fun moveRunningTaskToDesktop_remoteTransition_usesOneShotHandler() {
+    val transitionHandlerArgCaptor = ArgumentCaptor.forClass(TransitionHandler::class.java)
+    whenever(
+      transitions.startTransition(anyInt(), any(), transitionHandlerArgCaptor.capture())
+    ).thenReturn(Binder())
+
+    controller.moveRunningTaskToDesktop(
+      task = setUpFullscreenTask(),
+      transitionSource = UNKNOWN,
+      remoteTransition = RemoteTransition(spy(TestRemoteTransition())))
+
+    verify(desktopModeEnterExitTransitionListener).onEnterDesktopModeTransitionStarted(FREEFORM_ANIMATION_DURATION)
+    assertIs<OneShotRemoteHandler>(transitionHandlerArgCaptor.value)
   }
 
   @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageOrderOperatorTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageOrderOperatorTests.kt
new file mode 100644
index 0000000..3b4a86a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageOrderOperatorTests.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.wm.shell.splitscreen
+
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.icons.IconProvider
+import com.android.wm.shell.Flags.enableFlexibleSplit
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33
+import com.android.wm.shell.splitscreen.StageTaskListener.StageListenerCallbacks
+import com.android.wm.shell.windowdecor.WindowDecorViewModel
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import java.util.Optional
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class StageOrderOperatorTests : ShellTestCase() {
+
+    @Mock
+    lateinit var mTaskOrganizer: ShellTaskOrganizer
+    @Mock
+    lateinit var mSyncQueue: SyncTransactionQueue
+    @Mock
+    lateinit var stageListenerCallbacks: StageListenerCallbacks
+    @Mock
+    lateinit var iconProvider: IconProvider
+    @Mock
+    lateinit var windowDecorViewModel: Optional<WindowDecorViewModel>
+
+    lateinit var stageOrderOperator: StageOrderOperator
+
+    @Before
+    fun setup() {
+        stageOrderOperator = StageOrderOperator(
+            context,
+            mTaskOrganizer,
+            DEFAULT_DISPLAY,
+            stageListenerCallbacks,
+            mSyncQueue,
+            iconProvider,
+            windowDecorViewModel,
+            )
+        assert(stageOrderOperator.activeStages.size == 0)
+    }
+
+    @Test
+    fun activeStages_2_2app_50_50_split() {
+        assumeTrue(enableFlexibleSplit())
+
+        stageOrderOperator.onEnteringSplit(SNAP_TO_2_50_50)
+        assert(stageOrderOperator.activeStages.size == 2)
+    }
+
+    @Test
+    fun activeStages_2_2app_33_66_split() {
+        assumeTrue(enableFlexibleSplit())
+
+        stageOrderOperator.onEnteringSplit(SNAP_TO_2_33_66)
+        assert(stageOrderOperator.activeStages.size == 2)
+    }
+
+    @Test
+    fun activeStages_2_2app_66_33_split() {
+        assumeTrue(enableFlexibleSplit())
+
+        stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33)
+        assert(stageOrderOperator.activeStages.size == 2)
+    }
+
+    @Test
+    fun activateSameCountStage_noOp() {
+        assumeTrue(enableFlexibleSplit())
+
+        stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33)
+        stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33)
+        assert(stageOrderOperator.activeStages.size == 2)
+    }
+
+    @Test
+    fun deactivate_emptyActiveStages() {
+        assumeTrue(enableFlexibleSplit())
+
+        stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33)
+        stageOrderOperator.onExitingSplit()
+        assert(stageOrderOperator.activeStages.isEmpty())
+    }
+
+    @Test
+    fun swapDividerPos_twoApps() {
+        assumeTrue(enableFlexibleSplit())
+
+        stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33)
+        val stageIndex0: StageTaskListener = stageOrderOperator.activeStages[0]
+        val stageIndex1: StageTaskListener = stageOrderOperator.activeStages[1]
+
+        stageOrderOperator.onDoubleTappedDivider()
+        val newStageIndex0: StageTaskListener = stageOrderOperator.activeStages[0]
+        val newStageIndex1: StageTaskListener = stageOrderOperator.activeStages[1]
+
+        assert(stageIndex0 == newStageIndex1)
+        assert(stageIndex1 == newStageIndex0)
+    }
+
+    @Test
+    fun swapDividerPos_twiceNoOp_twoApps() {
+        assumeTrue(enableFlexibleSplit())
+
+        stageOrderOperator.onEnteringSplit(SNAP_TO_2_66_33)
+        val stageIndex0: StageTaskListener = stageOrderOperator.activeStages[0]
+        val stageIndex1: StageTaskListener = stageOrderOperator.activeStages[1]
+
+        stageOrderOperator.onDoubleTappedDivider()
+        stageOrderOperator.onDoubleTappedDivider()
+        val newStageIndex0: StageTaskListener = stageOrderOperator.activeStages[0]
+        val newStageIndex1: StageTaskListener = stageOrderOperator.activeStages[1]
+
+        assert(stageIndex0 == newStageIndex0)
+        assert(stageIndex1 == newStageIndex1)
+    }
+}
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 88f62d1..0214da4 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
@@ -388,7 +388,7 @@
     }
 
     @Test
-    fun testOnDecorMaximizedOrRestored_togglesTaskSize() {
+    fun testOnDecorMaximizedOrRestored_togglesTaskSize_maximize() {
         val maxOrRestoreListenerCaptor = forClass(Function0::class.java)
                 as ArgumentCaptor<Function0<Unit>>
         val decor = createOpenTaskDecoration(
@@ -409,6 +409,52 @@
     }
 
     @Test
+    fun testOnDecorMaximizedOrRestored_togglesTaskSize_maximizeFromMaximizedSize() {
+        val maxOrRestoreListenerCaptor = forClass(Function0::class.java)
+                as ArgumentCaptor<Function0<Unit>>
+        val decor = createOpenTaskDecoration(
+            windowingMode = WINDOWING_MODE_FREEFORM,
+            onMaxOrRestoreListenerCaptor = maxOrRestoreListenerCaptor
+        )
+        val movedMaximizedBounds = Rect(STABLE_BOUNDS)
+        movedMaximizedBounds.offset(10, 10)
+        decor.mTaskInfo.configuration.windowConfiguration.bounds.set(movedMaximizedBounds)
+
+        maxOrRestoreListenerCaptor.value.invoke()
+
+        verify(mockDesktopTasksController).toggleDesktopTaskSize(
+            decor.mTaskInfo,
+            ToggleTaskSizeInteraction(
+                ToggleTaskSizeInteraction.Direction.MAXIMIZE,
+                ToggleTaskSizeInteraction.Source.MAXIMIZE_MENU_TO_MAXIMIZE,
+                InputMethod.UNKNOWN_INPUT_METHOD
+            )
+        )
+    }
+
+    @Test
+    fun testOnDecorMaximizedOrRestored_togglesTaskSize_restore() {
+        val maxOrRestoreListenerCaptor = forClass(Function0::class.java)
+                as ArgumentCaptor<Function0<Unit>>
+        val decor = createOpenTaskDecoration(
+            windowingMode = WINDOWING_MODE_FREEFORM,
+            onMaxOrRestoreListenerCaptor = maxOrRestoreListenerCaptor
+        )
+        decor.mTaskInfo.configuration.windowConfiguration.bounds.set(STABLE_BOUNDS)
+
+        maxOrRestoreListenerCaptor.value.invoke()
+
+        verify(mockDesktopTasksController).toggleDesktopTaskSize(
+            decor.mTaskInfo,
+            ToggleTaskSizeInteraction(
+                ToggleTaskSizeInteraction.Direction.RESTORE,
+                ToggleTaskSizeInteraction.Source.MAXIMIZE_MENU_TO_RESTORE,
+                InputMethod.UNKNOWN_INPUT_METHOD
+            )
+        )
+    }
+
+    @Test
     fun testOnDecorMaximizedOrRestored_closesMenus() {
         val maxOrRestoreListenerCaptor = forClass(Function0::class.java)
                 as ArgumentCaptor<Function0<Unit>>
@@ -591,7 +637,8 @@
         verify(mockDesktopTasksController).moveTaskToDesktop(
             eq(decor.mTaskInfo.taskId),
             any(),
-            eq(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON)
+            eq(DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON),
+            anyOrNull()
         )
     }
 
@@ -824,7 +871,7 @@
         )
 
         verify(mockDesktopTasksController, times(1))
-            .moveTaskToDesktop(any(), any(), any())
+            .moveTaskToDesktop(any(), any(), any(), anyOrNull())
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index 6be234e..7a37c5e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -149,7 +149,6 @@
     protected val mockCaptionHandleRepository = mock<WindowDecorCaptionHandleRepository>()
     protected val mockDesktopRepository: DesktopRepository = mock<DesktopRepository>()
     protected val motionEvent = mock<MotionEvent>()
-    val displayController = mock<DisplayController>()
     val displayLayout = mock<DisplayLayout>()
     protected lateinit var spyContext: TestableContext
     private lateinit var desktopModeEventLogger: DesktopModeEventLogger
@@ -255,7 +254,7 @@
             argumentCaptor<DesktopModeKeyguardChangeListener>()
         verify(mockShellController).addKeyguardChangeListener(keyguardChangedCaptor.capture())
         desktopModeOnKeyguardChangedListener = keyguardChangedCaptor.firstValue
-        whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
+        whenever(mockDisplayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
         whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
             (i.arguments.first() as Rect).set(STABLE_BOUNDS)
         }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 5d5d1f2..db7b1f2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.app.assist.AssistContent.EXTRA_SESSION_TRANSFER_WEB_URI;
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
@@ -157,6 +158,7 @@
     private static final Uri TEST_URI1 = Uri.parse("https://www.google.com/");
     private static final Uri TEST_URI2 = Uri.parse("https://docs.google.com/");
     private static final Uri TEST_URI3 = Uri.parse("https://slides.google.com/");
+    private static final Uri TEST_URI4 = Uri.parse("https://calendar.google.com/");
 
     @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
 
@@ -1322,11 +1324,11 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
-    public void capturedLink_handleMenuBrowserLinkSetToCapturedLinkIfValid() {
+    public void capturedLink_CapturedLinkUsedIfValidAndWebUriUnavailable() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
         final DesktopModeWindowDecoration decor = createWindowDecoration(
-                taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */,
-                TEST_URI3 /* generic link */);
+                taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
+                null /* session transfer uri */, TEST_URI4 /* generic link */);
 
         // Verify handle menu's browser link set as captured link
         createHandleMenu(decor);
@@ -1339,7 +1341,7 @@
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
         final DesktopModeWindowDecoration decor = createWindowDecoration(
                 taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
-                null /* generic link */);
+                null /* session transfer uri */, null /* generic link */);
         final ArgumentCaptor<Function1<Intent, Unit>> openInBrowserCaptor =
                 ArgumentCaptor.forClass(Function1.class);
 
@@ -1373,7 +1375,7 @@
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
         final DesktopModeWindowDecoration decor = createWindowDecoration(
                 taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
-                null /* generic link */);
+                null /* session transfer uri */, null /* generic link */);
         final ArgumentCaptor<Function1<Intent, Unit>> openInBrowserCaptor =
                 ArgumentCaptor.forClass(Function1.class);
 
@@ -1406,7 +1408,7 @@
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
         final DesktopModeWindowDecoration decor = createWindowDecoration(
                 taskInfo, TEST_URI1 /* captured link */, null /* web uri */,
-                null /* generic link */);
+                null /* session transfer uri */, null /* generic link */);
         final ArgumentCaptor<Function1<Intent, Unit>> openInBrowserCaptor =
                 ArgumentCaptor.forClass(Function1.class);
         createHandleMenu(decor);
@@ -1432,11 +1434,23 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
-    public void webUriLink_webUriLinkUsedWhenCapturedLinkUnavailable() {
+    public void webUriLink_webUriLinkUsedWhenWhenAvailable() {
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
         final DesktopModeWindowDecoration decor = createWindowDecoration(
-                taskInfo, null /* captured link */, TEST_URI2 /* web uri */,
-                TEST_URI3 /* generic link */);
+                taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */,
+                TEST_URI3 /* session transfer uri */, TEST_URI4 /* generic link */);
+        // Verify handle menu's browser link set as web uri link when captured link is unavailable
+        createHandleMenu(decor);
+        verifyHandleMenuCreated(TEST_URI3);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+    public void webUriLink_webUriLinkUsedWhenSessionTransferUriUnavailable() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+        final DesktopModeWindowDecoration decor = createWindowDecoration(
+                taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */,
+                null /* session transfer uri */, TEST_URI4 /* generic link */);
         // Verify handle menu's browser link set as web uri link when captured link is unavailable
         createHandleMenu(decor);
         verifyHandleMenuCreated(TEST_URI2);
@@ -1448,12 +1462,12 @@
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
         final DesktopModeWindowDecoration decor = createWindowDecoration(
                 taskInfo, null /* captured link */, null /* web uri */,
-                TEST_URI3 /* generic link */);
+                null /* session transfer uri */, TEST_URI4 /* generic link */);
 
         // Verify handle menu's browser link set as generic link when captured link and web uri link
         // are unavailable
         createHandleMenu(decor);
-        verifyHandleMenuCreated(TEST_URI3);
+        verifyHandleMenuCreated(TEST_URI4);
     }
 
     @Test
@@ -1637,7 +1651,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
-    public void browserApp_webUriUsedForBrowserApp() {
+    public void browserApp_transferSessionUriUsedForBrowserAppWhenAvailable() {
         // Make {@link AppToWebUtils#isBrowserApp} return true
         ResolveInfo resolveInfo = new ResolveInfo();
         resolveInfo.handleAllWebDataURI = true;
@@ -1648,7 +1662,7 @@
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
         final DesktopModeWindowDecoration decor = createWindowDecoration(
                 taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */,
-                TEST_URI3 /* generic link */);
+                null /* transfer session uri */, TEST_URI4 /* generic link */);
 
         // Verify web uri used for browser applications
         createHandleMenu(decor);
@@ -1656,6 +1670,27 @@
     }
 
 
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
+    public void browserApp_webUriUsedForBrowserAppWhenTransferSessionUriUnavailable() {
+        // Make {@link AppToWebUtils#isBrowserApp} return true
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.handleAllWebDataURI = true;
+        resolveInfo.activityInfo = createActivityInfo();
+        when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(), anyInt()))
+                .thenReturn(List.of(resolveInfo));
+
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+        final DesktopModeWindowDecoration decor = createWindowDecoration(
+                taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */,
+                TEST_URI3 /* transfer session uri */, TEST_URI4 /* generic link */);
+
+        // Verify web uri used for browser applications
+        createHandleMenu(decor);
+        verifyHandleMenuCreated(TEST_URI3);
+    }
+
+
     private void verifyHandleMenuCreated(@Nullable Uri uri) {
         verify(mMockHandleMenuFactory).create(any(), any(), anyInt(), any(), any(),
                 any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(),
@@ -1692,10 +1727,11 @@
 
     private DesktopModeWindowDecoration createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo, @Nullable Uri capturedLink,
-            @Nullable Uri webUri, @Nullable Uri genericLink) {
+            @Nullable Uri webUri, @Nullable Uri sessionTransferUri, @Nullable Uri genericLink) {
         taskInfo.capturedLink = capturedLink;
         taskInfo.capturedLinkTimestamp = System.currentTimeMillis();
         mAssistContent.setWebUri(webUri);
+        mAssistContent.getExtras().putObject(EXTRA_SESSION_TRANSFER_WEB_URI, sessionTransferUri);
         final String genericLinkString = genericLink == null ? null : genericLink.toString();
         doReturn(genericLinkString).when(mMockGenericLinksParser).getGenericLink(any());
         // Relayout to set captured link
diff --git a/media/java/android/media/IMediaRoute2ProviderService.aidl b/media/java/android/media/IMediaRoute2ProviderService.aidl
index eee3d22..714cacb 100644
--- a/media/java/android/media/IMediaRoute2ProviderService.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderService.aidl
@@ -33,6 +33,23 @@
 
     void requestCreateSession(long requestId, String packageName, String routeId,
             in @nullable Bundle sessionHints);
+    /**
+     * Requests the creation of a system media routing session.
+     *
+     * @param requestId The id of the request.
+     * @param uid The uid of the package whose media to route, or
+     *     {@link android.os.Process#INVALID_UID} if routing should not be restricted to a specific
+     *     uid.
+     * @param packageName The name of the package whose media to route.
+     * @param routeId The id of the route to be initially selected.
+     * @param sessionHints An optional bundle with parameters.
+     */
+    void requestCreateSystemMediaSession(
+            long requestId,
+            int uid,
+            String packageName,
+            String routeId,
+            in @nullable Bundle sessionHints);
     void selectRoute(long requestId, String sessionId, String routeId);
     void deselectRoute(long requestId, String sessionId, String routeId);
     void transferToRoute(long requestId, String sessionId, String routeId);
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index a3ad340..037b97a 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -917,6 +917,17 @@
     }
 
     /**
+     * Returns whether this route supports routing of the system media.
+     *
+     * @hide
+     */
+    public boolean supportsSystemMediaRouting() {
+        return (mRoutingTypeFlags
+                        & (FLAG_ROUTING_TYPE_SYSTEM_VIDEO | FLAG_ROUTING_TYPE_SYSTEM_AUDIO))
+                != 0;
+    }
+
+    /**
      * Returns true if the route info has all of the required field.
      * A route is valid if and only if it is obtained from
      * {@link com.android.server.media.MediaRouterService}.
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 547099f..09f40e0 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -18,14 +18,21 @@
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
+import static java.util.Objects.requireNonNull;
+
+import android.Manifest;
 import android.annotation.CallSuper;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.app.Service;
 import android.content.Intent;
+import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
+import android.media.audiopolicy.AudioPolicy;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -36,6 +43,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.LongSparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.media.flags.Flags;
@@ -47,7 +55,6 @@
 import java.util.Collection;
 import java.util.Deque;
 import java.util.List;
-import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -83,8 +90,7 @@
     public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
 
     /**
-     * {@link Intent} action that indicates that the declaring service supports routing of the
-     * system media.
+     * A category that indicates that the declaring service supports routing of the system media.
      *
      * <p>Providers must include this action if they intend to publish routes that support the
      * system media, as described by {@link MediaRoute2Info#getSupportedRoutingTypes()}.
@@ -94,7 +100,7 @@
      */
     // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
     @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
-    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    @SdkConstant(SdkConstant.SdkConstantType.INTENT_CATEGORY)
     public static final String SERVICE_INTERFACE_SYSTEM_MEDIA =
             "android.media.MediaRoute2ProviderService.SYSTEM_MEDIA";
 
@@ -165,6 +171,16 @@
     @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
     public static final int REASON_UNIMPLEMENTED = 5;
 
+    /**
+     * The request has failed because the provider has failed to route system media.
+     *
+     * @see #notifyRequestFailed
+     * @hide
+     */
+    // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
+    @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
+    public static final int REASON_FAILED_TO_REROUTE_SYSTEM_MEDIA = 6;
+
     /** @hide */
     @IntDef(
             prefix = "REASON_",
@@ -174,7 +190,8 @@
                 REASON_NETWORK_ERROR,
                 REASON_ROUTE_NOT_AVAILABLE,
                 REASON_INVALID_COMMAND,
-                REASON_UNIMPLEMENTED
+                REASON_UNIMPLEMENTED,
+                REASON_FAILED_TO_REROUTE_SYSTEM_MEDIA
             })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Reason {}
@@ -187,15 +204,28 @@
     private final AtomicBoolean mStatePublishScheduled = new AtomicBoolean(false);
     private final AtomicBoolean mSessionUpdateScheduled = new AtomicBoolean(false);
     private MediaRoute2ProviderServiceStub mStub;
+    /** Populated by system_server in {@link #setCallback}. Monotonically non-null. */
     private IMediaRoute2ProviderServiceCallback mRemoteCallback;
     private volatile MediaRoute2ProviderInfo mProviderInfo;
 
     @GuardedBy("mRequestIdsLock")
     private final Deque<Long> mRequestIds = new ArrayDeque<>(MAX_REQUEST_IDS_SIZE);
 
+    /**
+     * Maps system media session creation request ids to a package uid whose media to route. The
+     * value may be {@link Process#INVALID_UID} for routing sessions that don't affect a specific
+     * package (for example, if they affect the entire system).
+     */
+    @GuardedBy("mRequestIdsLock")
+    private final LongSparseArray<Integer> mSystemMediaSessionCreationRequests =
+            new LongSparseArray<>();
+
     @GuardedBy("mSessionLock")
     private final ArrayMap<String, RoutingSessionInfo> mSessionInfos = new ArrayMap<>();
 
+    @GuardedBy("mSessionLock")
+    private final ArrayMap<String, MediaStreams> mOngoingMediaStreams = new ArrayMap<>();
+
     public MediaRoute2ProviderService() {
         mHandler = new Handler(Looper.getMainLooper());
     }
@@ -282,7 +312,7 @@
      */
     public final void notifySessionCreated(long requestId,
             @NonNull RoutingSessionInfo sessionInfo) {
-        Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+        requireNonNull(sessionInfo, "sessionInfo must not be null");
 
         if (DEBUG) {
             Log.d(TAG, "notifySessionCreated: Creating a session. requestId=" + requestId
@@ -326,17 +356,129 @@
      * @param formats the {@link MediaStreamsFormats} that describes the format for the {@link
      *     MediaStreams} to return.
      * @return a {@link MediaStreams} instance that holds the media streams to route as part of the
-     *     newly created routing session.
+     *     newly created routing session. May be null if system media capture failed, in which case
+     *     you can ignore the return value, as you will receive a call to {@link #onReleaseSession}
+     *     where you can clean up this session
      * @hide
      */
     // TODO: b/362507305 - Unhide once the implementation and CTS are in place.
     @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
-    @NonNull
+    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @Nullable
     public final MediaStreams notifySystemMediaSessionCreated(
             long requestId,
             @NonNull RoutingSessionInfo sessionInfo,
             @NonNull MediaStreamsFormats formats) {
-        throw new UnsupportedOperationException();
+        requireNonNull(sessionInfo, "sessionInfo must not be null");
+        requireNonNull(formats, "formats must not be null");
+        if (DEBUG) {
+            Log.d(
+                    TAG,
+                    "notifySystemMediaSessionCreated: Creating a session. requestId="
+                            + requestId
+                            + ", sessionInfo="
+                            + sessionInfo);
+        }
+
+        Integer uid;
+        synchronized (mRequestIdsLock) {
+            uid = mSystemMediaSessionCreationRequests.get(requestId);
+            mSystemMediaSessionCreationRequests.remove(requestId);
+        }
+
+        if (uid == null) {
+            throw new IllegalStateException(
+                    "Unexpected system routing session created (request id="
+                            + requestId
+                            + "):"
+                            + sessionInfo);
+        }
+
+        if (mRemoteCallback == null) {
+            throw new IllegalStateException("Unexpected: remote callback is null.");
+        }
+
+        int routingTypes = 0;
+        var providerInfo = mProviderInfo;
+        for (String selectedRouteId : sessionInfo.getSelectedRoutes()) {
+            MediaRoute2Info route = providerInfo.mRoutes.get(selectedRouteId);
+            if (route == null) {
+                throw new IllegalArgumentException(
+                        "Invalid selected route with id: " + selectedRouteId);
+            }
+            routingTypes |= route.getSupportedRoutingTypes();
+        }
+
+        if ((routingTypes & MediaRoute2Info.FLAG_ROUTING_TYPE_SYSTEM_AUDIO) == 0) {
+            // TODO: b/380431086 - Populate video stream once we add support for video.
+            throw new IllegalArgumentException(
+                    "Selected routes for system media don't support any system media routing"
+                            + " types.");
+        }
+
+        AudioFormat audioFormat = formats.mAudioFormat;
+        var mediaStreamsBuilder = new MediaStreams.Builder();
+        if (audioFormat != null) {
+            populateAudioStream(audioFormat, uid, mediaStreamsBuilder);
+        }
+        // TODO: b/380431086 - Populate video stream once we add support for video.
+
+        MediaStreams streams = mediaStreamsBuilder.build();
+        var audioRecord = streams.mAudioRecord;
+        if (audioRecord == null) {
+            Log.e(
+                    TAG,
+                    "Audio record is not populated. Returning an empty stream and scheduling the"
+                            + " session release for: "
+                            + sessionInfo);
+            mHandler.post(() -> onReleaseSession(REQUEST_ID_NONE, sessionInfo.getOriginalId()));
+            notifyRequestFailed(requestId, REASON_FAILED_TO_REROUTE_SYSTEM_MEDIA);
+            return null;
+        }
+
+        synchronized (mSessionLock) {
+            try {
+                mRemoteCallback.notifySessionCreated(requestId, sessionInfo);
+            } catch (RemoteException ex) {
+                ex.rethrowFromSystemServer();
+            }
+            mOngoingMediaStreams.put(sessionInfo.getOriginalId(), streams);
+            return streams;
+        }
+    }
+
+    @RequiresPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)
+    private void populateAudioStream(
+            AudioFormat audioFormat, int uid, MediaStreams.Builder builder) {
+        var audioAttributes =
+                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
+        var audioMixingRuleBuilder =
+                new AudioMixingRule.Builder()
+                        .addRule(audioAttributes, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
+        if (uid != Process.INVALID_UID) {
+            audioMixingRuleBuilder.addMixRule(AudioMixingRule.RULE_MATCH_UID, uid);
+        }
+
+        AudioMix mix =
+                new AudioMix.Builder(audioMixingRuleBuilder.build())
+                        .setFormat(audioFormat)
+                        .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK)
+                        .build();
+        AudioPolicy audioPolicy =
+                new AudioPolicy.Builder(this).setLooper(mHandler.getLooper()).addMix(mix).build();
+        var audioManager = getSystemService(AudioManager.class);
+        if (audioManager == null) {
+            Log.e(TAG, "Couldn't fetch the audio manager.");
+            return;
+        }
+        audioManager.registerAudioPolicy(audioPolicy);
+        var audioRecord = audioPolicy.createAudioRecordSink(mix);
+        if (audioRecord == null) {
+            Log.e(TAG, "Audio record creation failed.");
+            audioManager.unregisterAudioPolicy(audioPolicy);
+            return;
+        }
+        builder.setAudioStream(audioPolicy, audioRecord);
     }
 
     /**
@@ -344,7 +486,7 @@
      * {@link RoutingSessionInfo#getSelectedRoutes() selected routes} are changed.
      */
     public final void notifySessionUpdated(@NonNull RoutingSessionInfo sessionInfo) {
-        Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+        requireNonNull(sessionInfo, "sessionInfo must not be null");
 
         if (DEBUG) {
             Log.d(TAG, "notifySessionUpdated: Updating session id=" + sessionInfo);
@@ -379,6 +521,7 @@
         RoutingSessionInfo sessionInfo;
         synchronized (mSessionLock) {
             sessionInfo = mSessionInfos.remove(sessionId);
+            maybeReleaseMediaStreams(sessionId);
 
             if (sessionInfo == null) {
                 Log.w(TAG, "notifySessionReleased: Ignoring unknown session info.");
@@ -396,6 +539,34 @@
         }
     }
 
+    /** Releases any system media routing resources associated with the given {@code sessionId}. */
+    private void maybeReleaseMediaStreams(String sessionId) {
+        if (!Flags.enableMirroringInMediaRouter2()) {
+            return;
+        }
+        synchronized (mSessionLock) {
+            var streams = mOngoingMediaStreams.remove(sessionId);
+            if (streams != null) {
+                releaseAudioStream(streams.mAudioPolicy, streams.mAudioRecord);
+                // TODO: b/380431086: Release the video stream once implemented.
+            }
+        }
+    }
+
+    // We cannot reach the code that requires MODIFY_AUDIO_ROUTING without holding it.
+    @SuppressWarnings("MissingPermission")
+    private void releaseAudioStream(AudioPolicy audioPolicy, AudioRecord audioRecord) {
+        if (audioPolicy == null) {
+            return;
+        }
+        var audioManager = getSystemService(AudioManager.class);
+        if (audioManager == null) {
+            return;
+        }
+        audioRecord.stop();
+        audioManager.unregisterAudioPolicy(audioPolicy);
+    }
+
     /**
      * Notifies to the client that the request has failed.
      *
@@ -569,7 +740,7 @@
      * Updates routes of the provider and notifies the system media router service.
      */
     public final void notifyRoutes(@NonNull Collection<MediaRoute2Info> routes) {
-        Objects.requireNonNull(routes, "routes must not be null");
+        requireNonNull(routes, "routes must not be null");
         List<MediaRoute2Info> sanitizedRoutes = new ArrayList<>(routes.size());
 
         for (MediaRoute2Info route : routes) {
@@ -763,6 +934,32 @@
         }
 
         @Override
+        public void requestCreateSystemMediaSession(
+                long requestId,
+                int uid,
+                String packageName,
+                String routeId,
+                @Nullable Bundle sessionHints) {
+            if (!checkCallerIsSystem()) {
+                return;
+            }
+            if (!checkRouteIdIsValid(routeId, "requestCreateSession")) {
+                return;
+            }
+            synchronized (mRequestIdsLock) {
+                mSystemMediaSessionCreationRequests.put(requestId, uid);
+            }
+            mHandler.sendMessage(
+                    obtainMessage(
+                            MediaRoute2ProviderService::onCreateSystemRoutingSession,
+                            MediaRoute2ProviderService.this,
+                            requestId,
+                            packageName,
+                            routeId,
+                            sessionHints));
+        }
+
+        @Override
         public void selectRoute(long requestId, String sessionId, String routeId) {
             if (!checkCallerIsSystem()) {
                 return;
@@ -825,6 +1022,10 @@
             if (!checkSessionIdIsValid(sessionId, "releaseSession")) {
                 return;
             }
+            // We proactively release the system media routing once the system requests it, to
+            // ensure it happens immediately.
+            maybeReleaseMediaStreams(sessionId);
+
             addRequestId(requestId);
             mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession,
                     MediaRoute2ProviderService.this, requestId, sessionId));
@@ -843,12 +1044,14 @@
     @FlaggedApi(Flags.FLAG_ENABLE_MIRRORING_IN_MEDIA_ROUTER_2)
     public static final class MediaStreams {
 
-        private final AudioRecord mAudioRecord;
+        @Nullable private final AudioPolicy mAudioPolicy;
+        @Nullable private final AudioRecord mAudioRecord;
 
         // TODO: b/380431086: Add the video equivalent.
 
-        private MediaStreams(AudioRecord mAudioRecord) {
-            this.mAudioRecord = mAudioRecord;
+        private MediaStreams(Builder builder) {
+            this.mAudioPolicy = builder.mAudioPolicy;
+            this.mAudioRecord = builder.mAudioRecord;
         }
 
         /**
@@ -859,8 +1062,33 @@
         public AudioRecord getAudioRecord() {
             return mAudioRecord;
         }
+
+        /**
+         * Builder for {@link MediaStreams}.
+         *
+         * @hide
+         */
+        public static final class Builder {
+
+            @Nullable private AudioPolicy mAudioPolicy;
+            @Nullable private AudioRecord mAudioRecord;
+
+            /** Populates system media audio-related structures. */
+            public Builder setAudioStream(
+                    @NonNull AudioPolicy audioPolicy, @NonNull AudioRecord audioRecord) {
+                mAudioPolicy = requireNonNull(audioPolicy);
+                mAudioRecord = requireNonNull(audioRecord);
+                return this;
+            }
+
+            /** Builds a {@link MediaStreams} instance. */
+            public MediaStreams build() {
+                return new MediaStreams(this);
+            }
+        }
     }
 
+
     /**
      * Holds the formats to encode media data to be read from {@link MediaStreams}.
      *
@@ -911,7 +1139,7 @@
              */
             @NonNull
             public Builder setAudioFormat(@NonNull AudioFormat audioFormat) {
-                this.mAudioFormat = Objects.requireNonNull(audioFormat);
+                this.mAudioFormat = requireNonNull(audioFormat);
                 return this;
             }
 
diff --git a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 607ec1c..9ae0f03 100644
--- a/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/packages/NeuralNetworks/service/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -16,8 +16,6 @@
 
 package com.android.server.ondeviceintelligence;
 
-import static android.app.ondeviceintelligence.flags.Flags.enableOnDeviceIntelligenceModule;
-
 import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS;
 import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.DEVICE_CONFIG_UPDATE_BUNDLE_KEY;
 import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BROADCAST_INTENT;
@@ -180,10 +178,8 @@
         publishBinderService(
                 Context.ON_DEVICE_INTELLIGENCE_SERVICE, getOnDeviceIntelligenceManagerService(),
                 /* allowIsolated = */ true);
-        if (enableOnDeviceIntelligenceModule()) {
-            LocalManagerRegistry.addManager(OnDeviceIntelligenceManagerLocal.class,
+        LocalManagerRegistry.addManager(OnDeviceIntelligenceManagerLocal.class,
                     this::getRemoteInferenceServiceUid);
-        }
     }
 
     @Override
@@ -198,20 +194,6 @@
         }
     }
 
-    @Override
-    public void onUserUnlocked(@NonNull TargetUser user) {
-        Slog.d(TAG, "onUserUnlocked: " + user.getUserHandle());
-        //connect to remote services(if available) during boot.
-        if (user.getUserHandle().equals(UserHandle.SYSTEM)) {
-            try {
-                ensureRemoteInferenceServiceInitialized(/* throwServiceIfInvalid */ false);
-                ensureRemoteIntelligenceServiceInitialized(/* throwServiceIfInvalid */ false);
-            } catch (Exception e) {
-                Slog.w(TAG, "Couldn't pre-start remote ondeviceintelligence services.", e);
-            }
-        }
-    }
-
     private void onDeviceConfigChange(@NonNull Set<String> keys) {
         if (keys.contains(KEY_SERVICE_ENABLED)) {
             mIsServiceEnabled = isServiceEnabled();
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml b/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml
index cc948a6..fd8cecb 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/values-v35/styles_expressive.xml
@@ -22,12 +22,13 @@
         <item name="iconGravity">textTop</item>
     </style>
 
-    <style name="SettingsLibActionButton.Expressive.Label" parent="SettingsLibTextAppearance.Emphasized.Title.Small">
+    <style name="SettingsLibActionButton.Expressive.Label" parent="">
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:minWidth">@dimen/settingslib_expressive_space_small3</item>
         <item name="android:minHeight">@dimen/settingslib_expressive_space_small3</item>
-        <item name="android:textColor">@color/settingslib_materialColorOnSurface</item>
+        <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleSmall.Emphasized</item>
+        <item name="android:textColor">@color/settingslib_text_color_primary</item>
         <item name="android:layout_gravity">center</item>
     </style>
 
diff --git a/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml b/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml
index 716ed41..9018bac 100644
--- a/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml
+++ b/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml
@@ -40,7 +40,6 @@
 
             <ImageView
                 android:id="@android:id/icon"
-                android:src="@drawable/settingslib_arrow_drop_down"
                 android:layout_width="@dimen/settingslib_expressive_space_medium3"
                 android:layout_height="@dimen/settingslib_expressive_space_medium3"
                 android:scaleType="centerInside"/>
@@ -60,16 +59,12 @@
                 android:id="@android:id/title"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:hyphenationFrequency="normalFast"
-                android:lineBreakWordStyle="phrase"
                 android:textAppearance="@style/TextAppearance.CardTitle.SettingsLib"/>
 
             <TextView
                 android:id="@android:id/summary"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:hyphenationFrequency="normalFast"
-                android:lineBreakWordStyle="phrase"
                 android:textAppearance="@style/TextAppearance.CardSummary.SettingsLib"/>
 
         </LinearLayout>
diff --git a/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml b/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml
index 4cbdea5..287b13f 100644
--- a/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml
+++ b/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml
@@ -17,14 +17,12 @@
 
 <resources>
     <style name="TextAppearance.CardTitle.SettingsLib"
-        parent="@style/TextAppearance.PreferenceTitle.SettingsLib">
+        parent="@style/TextAppearance.SettingsLib.TitleMedium.Emphasized">
         <item name="android:textColor">@color/settingslib_materialColorOnPrimary</item>
-        <item name="android:textSize">20sp</item>
     </style>
 
     <style name="TextAppearance.CardSummary.SettingsLib"
-        parent="@style/TextAppearance.PreferenceSummary.SettingsLib">
+        parent="@style/TextAppearance.SettingsLib.LabelMedium">
         <item name="android:textColor">@color/settingslib_materialColorOnSecondary</item>
-        <item name="android:textSize">14sp</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/drawable-v35/settingslib_expressive_icon_back.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/drawable-v35/settingslib_expressive_icon_back.xml
index ccbe20e..9986a60 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/drawable-v35/settingslib_expressive_icon_back.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/drawable-v35/settingslib_expressive_icon_back.xml
@@ -17,12 +17,9 @@
 
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
     <item
-        android:start="16dp"
-        android:end="16dp"
-        android:top="4dp"
-        android:bottom="4dp">
+        android:start="16dp">
         <shape>
-            <size android:width="32dp" android:height="40dp" />
+            <size android:width="40dp" android:height="40dp" />
             <solid android:color="@color/settingslib_materialColorSurfaceContainerHighest" />
             <corners
                 android:radius="100dp" />
@@ -30,23 +27,21 @@
     </item>
 
     <item
-        android:width="24dp"
-        android:height="24dp"
+        android:width="16dp"
+        android:height="16dp"
         android:gravity="center"
         android:start="16dp"
-        android:end="16dp"
-        android:top="4dp"
-        android:bottom="4dp">
+>
         <vector
-            android:width="24dp"
-            android:height="24dp"
-            android:viewportWidth="960"
-            android:viewportHeight="960"
+            android:width="16dp"
+            android:height="16dp"
+            android:viewportWidth="16"
+            android:viewportHeight="16"
             android:tint="@color/settingslib_materialColorOnSurfaceVariant"
             android:autoMirrored="true">
             <path
                 android:fillColor="@android:color/white"
-                android:pathData="M313,520L537,744L480,800L160,480L480,160L537,216L313,440L800,440L800,520L313,520Z"/>
+                android:pathData="M3.626,9L8.526,13.9C8.726,14.1 8.817,14.333 8.801,14.6C8.801,14.867 8.701,15.1 8.501,15.3C8.301,15.483 8.067,15.583 7.801,15.6C7.534,15.6 7.301,15.5 7.101,15.3L0.501,8.7C0.401,8.6 0.326,8.492 0.276,8.375C0.242,8.258 0.226,8.133 0.226,8C0.226,7.867 0.242,7.742 0.276,7.625C0.326,7.508 0.401,7.4 0.501,7.3L7.101,0.7C7.284,0.517 7.509,0.425 7.776,0.425C8.059,0.425 8.301,0.517 8.501,0.7C8.701,0.9 8.801,1.142 8.801,1.425C8.801,1.692 8.701,1.925 8.501,2.125L3.626,7H14.801C15.084,7 15.317,7.1 15.501,7.3C15.701,7.483 15.801,7.717 15.801,8C15.801,8.283 15.701,8.525 15.501,8.725C15.317,8.908 15.084,9 14.801,9H3.626Z"/>
         </vector>
     </item>
 </layer-list>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml
index e68253e..fadcf7b 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v35/themes.xml
@@ -18,7 +18,7 @@
     <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
         <item name="elevationOverlayEnabled">true</item>
         <item name="elevationOverlayColor">?attr/colorPrimary</item>
-        <item name="colorPrimary">@color/settingslib_materialColorInverseOnSurface</item>
+        <item name="colorPrimary">@color/settingslib_materialColorOnSurfaceInverse</item>
         <item name="colorAccent">@color/settingslib_materialColorPrimaryFixed</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles_expressive.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles_expressive.xml
index d58c2c2..37a7810 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/styles_expressive.xml
@@ -33,12 +33,12 @@
         <item name="contentScrim">@color/settingslib_materialColorSurfaceContainer</item>
     </style>
 
-    <style name="SettingsLibCollapsingToolbarTitle.Collapsed" parent="@android:style/TextAppearance.DeviceDefault.Headline">
+    <style name="SettingsLibCollapsingToolbarTitle.Collapsed" parent="@style/TextAppearance.SettingsLib.TitleLarge.Emphasized">
         <!--set dp because we don't want size adjust when font size change-->
-        <item name="android:textSize">20dp</item>
+        <item name="android:textSize">22dp</item>
     </style>
 
-    <style name="SettingsLibCollapsingToolbarTitle.Expanded" parent="CollapsingToolbarTitle.Collapsed">
+    <style name="SettingsLibCollapsingToolbarTitle.Expanded" parent="@style/TextAppearance.SettingsLib.DisplaySmall.Emphasized">
         <item name="android:textSize">36dp</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml
index f7c9aac..7c9d1a4 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v35/themes.xml
@@ -18,7 +18,7 @@
     <style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
         <item name="elevationOverlayEnabled">true</item>
         <item name="elevationOverlayColor">?attr/colorPrimary</item>
-        <item name="colorPrimary">@color/settingslib_materialColorInverseOnSurface</item>
+        <item name="colorPrimary">@color/settingslib_materialColorOnSurfaceInverse</item>
         <item name="colorAccent">@color/settingslib_materialColorPrimary</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
index 2edc001..43cf6aa 100644
--- a/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
+++ b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
@@ -26,7 +26,6 @@
 
         <ImageView
             android:id="@android:id/icon"
-            android:src="@drawable/settingslib_arrow_drop_down"
             style="@style/SettingsLibEntityHeaderIcon"/>
 
         <TextView
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 106802e..73728bcd 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -32,6 +32,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.ColorInt;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
 import com.android.settingslib.widget.mainswitch.R;
@@ -42,7 +43,7 @@
 /**
  * MainSwitchBar is a View with a customized Switch.
  * This component is used as the main switch of the page
- * to enable or disable the prefereces on the page.
+ * to enable or disable the preferences on the page.
  */
 public class MainSwitchBar extends LinearLayout implements OnCheckedChangeListener {
 
@@ -58,6 +59,8 @@
     protected CompoundButton mSwitch;
     private final View mFrameView;
 
+    private @Nullable PreChangeListener mPreChangeListener;
+
     public MainSwitchBar(Context context) {
         this(context, null);
     }
@@ -138,10 +141,20 @@
 
     @Override
     public boolean performClick() {
-        mSwitch.performClick();
+        if (callPreChangeListener()) {
+            mSwitch.performClick();
+        }
         return super.performClick();
     }
 
+    protected boolean callPreChangeListener() {
+        return mPreChangeListener == null || mPreChangeListener.preChange(!mSwitch.isChecked());
+    }
+
+    public void setPreChangeListener(@Nullable PreChangeListener preChangeListener) {
+        mPreChangeListener = preChangeListener;
+    }
+
     /**
      * Update the switch status
      */
@@ -271,7 +284,7 @@
         }
     }
 
-    static class SavedState extends BaseSavedState {
+    public static class SavedState extends BaseSavedState {
         boolean mChecked;
         boolean mVisible;
 
@@ -341,4 +354,16 @@
 
         requestLayout();
     }
+
+    /**
+     * Listener callback before switch is toggled.
+     */
+    public interface PreChangeListener {
+        /**
+         * Returns if the new value can be set.
+         *
+         * When false is return, the switch toggle is not triggered at all.
+         */
+        boolean preChange(boolean isCheck);
+    }
 }
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
index 6e86fa7..c1edbdc 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceMetadata.kt
@@ -107,20 +107,11 @@
      *
      * UI framework normally does not allow user to interact with the preference widget when it is
      * disabled.
-     *
-     * [dependencyOfEnabledState] is provided to support dependency, the [shouldDisableDependents]
-     * value of dependent preference is used to decide enabled state.
      */
-    fun isEnabled(context: Context): Boolean {
-        val dependency = dependencyOfEnabledState(context) ?: return true
-        return !dependency.shouldDisableDependents(context)
-    }
+    fun isEnabled(context: Context): Boolean = true
 
-    /** Returns the key of depended preference to decide the enabled state. */
-    fun dependencyOfEnabledState(context: Context): PreferenceMetadata? = null
-
-    /** Returns whether this preference's dependents should be disabled. */
-    fun shouldDisableDependents(context: Context): Boolean = !isEnabled(context)
+    /** Returns the keys of depended preferences. */
+    fun dependencies(context: Context): Array<String> = arrayOf()
 
     /** Returns if the preference is persistent in datastore. */
     fun isPersistent(context: Context): Boolean = this is PersistentPreference<*>
@@ -174,13 +165,11 @@
 }
 
 /** Metadata of preference group. */
-@AnyThread
-interface PreferenceGroup : PreferenceMetadata
+@AnyThread interface PreferenceGroup : PreferenceMetadata
 
 /** Metadata of preference category. */
 @AnyThread
-open class PreferenceCategory(override val key: String, override val title: Int) :
-    PreferenceGroup
+open class PreferenceCategory(override val key: String, override val title: Int) : PreferenceGroup
 
 /** Metadata of preference screen. */
 @AnyThread
diff --git a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
index 512ea3d..87bd261 100644
--- a/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
+++ b/packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/PreferenceTypes.kt
@@ -16,18 +16,10 @@
 
 package com.android.settingslib.metadata
 
-import android.content.Context
 import androidx.annotation.StringRes
 
-/**
- * Common base class for preferences that have two selectable states, save a boolean value, and may
- * have dependent preferences that are enabled/disabled based on the current state.
- */
-interface TwoStatePreference : PreferenceMetadata, PersistentPreference<Boolean>, BooleanValue {
-
-    override fun shouldDisableDependents(context: Context) =
-        storage(context).getBoolean(key) != true || super.shouldDisableDependents(context)
-}
+/** Common base class for preferences that have two selectable states and save a boolean value. */
+interface TwoStatePreference : PreferenceMetadata, PersistentPreference<Boolean>, BooleanValue
 
 /** A preference that provides a two-state toggleable option. */
 open class SwitchPreference
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index 31bb62d..a9e20f28 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -97,14 +97,14 @@
         val preferencesBuilder = ImmutableMap.builder<String, PreferenceHierarchyNode>()
         val dependenciesBuilder = ImmutableMultimap.builder<String, String>()
         val lifecycleAwarePreferences = mutableListOf<PreferenceLifecycleProvider>()
-        fun PreferenceMetadata.addDependency(dependency: PreferenceMetadata) {
-            dependenciesBuilder.put(key, dependency.key)
-        }
 
         fun PreferenceHierarchyNode.addNode() {
             metadata.let {
-                preferencesBuilder.put(it.key, this)
-                it.dependencyOfEnabledState(context)?.addDependency(it)
+                val key = it.key
+                preferencesBuilder.put(key, this)
+                for (dependency in it.dependencies(context)) {
+                    dependenciesBuilder.put(dependency, key)
+                }
                 if (it is PreferenceLifecycleProvider) lifecycleAwarePreferences.add(it)
             }
         }
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant12.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant12.xml
new file mode 100644
index 0000000..f125425
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant12.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral2_600" android:lStar="12"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant17.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant17.xml
new file mode 100644
index 0000000..36a7819
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant17.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral2_600" android:lStar="17"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant22.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant22.xml
new file mode 100644
index 0000000..0ef31d0
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant22.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral2_600" android:lStar="22"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant24.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant24.xml
new file mode 100644
index 0000000..6797f82
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant24.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral2_600" android:lStar="24"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant4.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant4.xml
new file mode 100644
index 0000000..ff7df55
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant4.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral2_600" android:lStar="4"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant6.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant6.xml
new file mode 100644
index 0000000..8da5daf
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant6.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral2_600" android:lStar="6"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant87.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant87.xml
new file mode 100644
index 0000000..227baee
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant87.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral2_600" android:lStar="87"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant92.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant92.xml
new file mode 100644
index 0000000..f456438
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant92.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral2_600" android:lStar="92"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant94.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant94.xml
new file mode 100644
index 0000000..bb4e03d
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant94.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral2_600" android:lStar="94"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant96.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant96.xml
new file mode 100644
index 0000000..949b196
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant96.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral2_600" android:lStar="96"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant98.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant98.xml
new file mode 100644
index 0000000..7e5ee24
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_neutral_variant98.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/system_neutral2_600" android:lStar="98"/>
+</selector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_check.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_check.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_check.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_check.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_close.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_close.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_close.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_close.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_switch_thumb_icon.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_switch_thumb_icon.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_switch_thumb_icon.xml
rename to packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_switch_thumb_icon.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml
index ea7baa4..2261e58 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_collapsable_textview.xml
@@ -40,7 +40,7 @@
         android:longClickable="false"
         android:maxLines="10"
         android:ellipsize="end"
-        android:textAppearance="@style/TextAppearance.TopIntroText"/>
+        android:textAppearance="@style/TextAppearance.SettingsLib.BodyLarge"/>
 
     <com.android.settingslib.widget.LinkableTextView
         android:id="@+id/settingslib_expressive_learn_more"
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_preference_text_frame.xml
similarity index 89%
rename from packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
rename to packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_preference_text_frame.xml
index c837ff4..db357f8 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_preference_text_frame.xml
@@ -32,8 +32,6 @@
         android:layout_gravity="start"
         android:textAlignment="viewStart"
         android:maxLines="2"
-        android:hyphenationFrequency="normalFast"
-        android:lineBreakWordStyle="phrase"
         android:textAppearance="?android:attr/textAppearanceListItem"
         android:ellipsize="marquee"/>
 
@@ -47,7 +45,5 @@
         android:textAlignment="viewStart"
         android:textAppearance="?android:attr/textAppearanceListItemSecondary"
         android:textColor="?android:attr/textColorSecondary"
-        android:maxLines="10"
-        android:hyphenationFrequency="normalFast"
-        android:lineBreakWordStyle="phrase"/>
+        android:maxLines="10"/>
 </RelativeLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml b/packages/SettingsLib/SettingsTheme/res/layout/settingslib_preference_category_no_title.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml
rename to packages/SettingsLib/SettingsTheme/res/layout/settingslib_preference_category_no_title.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
index 46ec62e..8873116 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
@@ -58,4 +58,39 @@
     <color name="settingslib_colorSurface">@color/settingslib_surface_dark</color>
 
     <color name="settingslib_list_divider_color">@android:color/system_neutral1_700</color>
+
+    <color name="settingslib_materialColorPrimary">@android:color/system_accent1_200</color>
+    <color name="settingslib_materialColorOnPrimary">@android:color/system_accent1_800</color>
+    <color name="settingslib_materialColorPrimaryContainer">@android:color/system_accent1_700</color>
+    <color name="settingslib_materialColorOnPrimaryContainer">@android:color/system_accent1_100</color>
+    <color name="settingslib_materialColorPrimaryInverse">@android:color/system_accent1_600</color>
+    <color name="settingslib_materialColorSecondary">@android:color/system_accent2_200</color>
+    <color name="settingslib_materialColorOnSecondary">@android:color/system_accent2_800</color>
+    <color name="settingslib_materialColorSecondaryContainer">@android:color/system_accent2_700</color>
+    <color name="settingslib_materialColorOnSecondaryContainer">@android:color/system_accent2_100</color>
+    <color name="settingslib_materialColorTertiary">@android:color/system_accent3_200</color>
+    <color name="settingslib_materialColorOnTertiary">@android:color/system_accent3_800</color>
+    <color name="settingslib_materialColorTertiaryContainer">@android:color/system_accent3_700</color>
+    <color name="settingslib_materialColorOnTertiaryContainer">@android:color/system_accent3_100</color>
+    <color name="settingslib_materialColorError">@color/settingslib_error_200</color>
+    <color name="settingslib_materialColorOnError">@color/settingslib_error_800</color>
+    <color name="settingslib_materialColorErrorContainer">@color/settingslib_error_700</color>
+    <color name="settingslib_materialColorOnErrorContainer">@color/settingslib_error_100</color>
+    <color name="settingslib_materialColorOutline">@android:color/system_neutral2_400</color>
+    <color name="settingslib_materialColorOutlineVariant">@android:color/system_neutral2_700</color>
+    <color name="settingslib_materialColorBackground">@color/settingslib_neutral_variant6</color>
+    <color name="settingslib_materialColorOnBackground">@android:color/system_neutral1_100</color>
+    <color name="settingslib_materialColorSurface">@color/settingslib_neutral_variant6</color>
+    <color name="settingslib_materialColorOnSurface">@android:color/system_neutral1_100</color>
+    <color name="settingslib_materialColorSurfaceVariant">@android:color/system_neutral2_700</color>
+    <color name="settingslib_materialColorOnSurfaceVariant">@android:color/system_neutral2_200</color>
+    <color name="settingslib_materialColorSurfaceInverse">@android:color/system_neutral1_100</color>
+    <color name="settingslib_materialColorOnSurfaceInverse">@android:color/system_neutral1_800</color>
+    <color name="settingslib_materialColorSurfaceBright">@color/settingslib_neutral_variant24</color>
+    <color name="settingslib_materialColorSurfaceDim">@color/settingslib_neutral_variant6</color>
+    <color name="settingslib_materialColorSurfaceContainer">@color/settingslib_neutral_variant12</color>
+    <color name="settingslib_materialColorSurfaceContainerLowest">@color/settingslib_neutral_variant4</color>
+    <color name="settingslib_materialColorSurfaceContainerLow">@android:color/system_neutral2_900</color>
+    <color name="settingslib_materialColorSurfaceContainerHigh">@color/settingslib_neutral_variant17</color>
+    <color name="settingslib_materialColorSurfaceContainerHighest">@color/settingslib_neutral_variant22</color>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml
index 8cfe54f..00a1f27 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v34/colors.xml
@@ -42,4 +42,39 @@
     <color name="settingslib_text_color_primary_device_default">@android:color/system_on_surface_dark</color>
     <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant-->
     <color name="settingslib_text_color_secondary_device_default">@android:color/system_on_surface_variant_dark</color>
+
+    <color name="settingslib_materialColorPrimary">@android:color/system_primary_dark</color>
+    <color name="settingslib_materialColorOnPrimary">@android:color/system_on_primary_dark</color>
+    <color name="settingslib_materialColorPrimaryContainer">@android:color/system_primary_container_dark</color>
+    <color name="settingslib_materialColorOnPrimaryContainer">@android:color/system_on_primary_container_dark</color>
+    <color name="settingslib_materialColorPrimaryInverse">@android:color/system_primary_light</color>
+    <color name="settingslib_materialColorSecondary">@android:color/system_secondary_dark</color>
+    <color name="settingslib_materialColorOnSecondary">@android:color/system_on_secondary_dark</color>
+    <color name="settingslib_materialColorSecondaryContainer">@android:color/system_secondary_container_dark</color>
+    <color name="settingslib_materialColorOnSecondaryContainer">@android:color/system_on_secondary_container_dark</color>
+    <color name="settingslib_materialColorTertiary">@android:color/system_tertiary_dark</color>
+    <color name="settingslib_materialColorOnTertiary">@android:color/system_on_tertiary_dark</color>
+    <color name="settingslib_materialColorTertiaryContainer">@android:color/system_tertiary_container_dark</color>
+    <color name="settingslib_materialColorOnTertiaryContainer">@android:color/system_on_tertiary_container_dark</color>
+    <color name="settingslib_materialColorError">@android:color/system_error_dark</color>
+    <color name="settingslib_materialColorOnError">@android:color/system_on_error_dark</color>
+    <color name="settingslib_materialColorErrorContainer">@android:color/system_error_container_dark</color>
+    <color name="settingslib_materialColorOnErrorContainer">@android:color/system_on_error_container_dark</color>
+    <color name="settingslib_materialColorOutline">@android:color/system_outline_dark</color>
+    <color name="settingslib_materialColorOutlineVariant">@android:color/system_outline_variant_dark</color>
+    <color name="settingslib_materialColorBackground">@android:color/system_background_dark</color>
+    <color name="settingslib_materialColorOnBackground">@android:color/system_on_background_dark</color>
+    <color name="settingslib_materialColorSurface">@android:color/system_surface_dark</color>
+    <color name="settingslib_materialColorOnSurface">@android:color/system_on_surface_dark</color>
+    <color name="settingslib_materialColorSurfaceVariant">@android:color/system_surface_variant_dark</color>
+    <color name="settingslib_materialColorOnSurfaceVariant">@android:color/system_on_surface_variant_dark</color>
+    <color name="settingslib_materialColorSurfaceInverse">@android:color/system_surface_light</color>
+    <color name="settingslib_materialColorOnSurfaceInverse">@android:color/system_on_surface_light</color>
+    <color name="settingslib_materialColorSurfaceBright">@android:color/system_surface_bright_dark</color>
+    <color name="settingslib_materialColorSurfaceDim">@android:color/system_surface_dim_dark</color>
+    <color name="settingslib_materialColorSurfaceContainer">@android:color/system_surface_container_dark</color>
+    <color name="settingslib_materialColorSurfaceContainerLow">@android:color/system_surface_container_low_dark</color>
+    <color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_dark</color>
+    <color name="settingslib_materialColorSurfaceContainerHigh">@android:color/system_surface_container_high_dark</color>
+    <color name="settingslib_materialColorSurfaceContainerHighest">@android:color/system_surface_container_highest_dark</color>
 </resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
index 84a3ed6..e31e801 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml
@@ -46,37 +46,4 @@
     <color name="settingslib_colorSurfaceHeader">@color/settingslib_materialColorSurfaceVariant</color>
 
     <color name="settingslib_text_color_preference_category_title">@color/settingslib_materialColorPrimary</color>
-
-    <color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_dark</color>
-    <color name="settingslib_materialColorOnSecondaryContainer">@android:color/system_on_secondary_container_dark</color>
-    <color name="settingslib_materialColorOnTertiaryContainer">@android:color/system_on_tertiary_container_dark</color>
-    <color name="settingslib_materialColorSurfaceContainerLow">@android:color/system_surface_container_low_dark</color>
-    <color name="settingslib_materialColorOnPrimaryContainer">@android:color/system_on_primary_container_dark</color>
-    <color name="settingslib_materialColorOnErrorContainer">@android:color/system_on_error_container_dark</color>
-    <color name="settingslib_materialColorInverseOnSurface">@android:color/system_on_surface_light</color>
-    <color name="settingslib_materialColorSecondaryContainer">@android:color/system_secondary_container_dark</color>
-    <color name="settingslib_materialColorErrorContainer">@android:color/system_error_container_dark</color>
-    <color name="settingslib_materialColorInversePrimary">@android:color/system_primary_light</color>
-    <color name="settingslib_materialColorInverseSurface">@android:color/system_surface_light</color>
-    <color name="settingslib_materialColorSurfaceVariant">@android:color/system_surface_variant_dark</color>
-    <color name="settingslib_materialColorTertiaryContainer">@android:color/system_tertiary_container_dark</color>
-    <color name="settingslib_materialColorPrimaryContainer">@android:color/system_primary_container_dark</color>
-    <color name="settingslib_materialColorOnBackground">@android:color/system_on_background_dark</color>
-    <color name="settingslib_materialColorOnSecondary">@android:color/system_on_secondary_dark</color>
-    <color name="settingslib_materialColorOnTertiary">@android:color/system_on_tertiary_dark</color>
-    <color name="settingslib_materialColorSurfaceDim">@android:color/system_surface_dim_dark</color>
-    <color name="settingslib_materialColorSurfaceBright">@android:color/system_surface_bright_dark</color>
-    <color name="settingslib_materialColorOnError">@android:color/system_on_error_dark</color>
-    <color name="settingslib_materialColorSurface">@android:color/system_surface_dark</color>
-    <color name="settingslib_materialColorSurfaceContainerHigh">@android:color/system_surface_container_high_dark</color>
-    <color name="settingslib_materialColorSurfaceContainerHighest">@android:color/system_surface_container_highest_dark</color>
-    <color name="settingslib_materialColorOnSurfaceVariant">@android:color/system_on_surface_variant_dark</color>
-    <color name="settingslib_materialColorOutline">@android:color/system_outline_dark</color>
-    <color name="settingslib_materialColorOutlineVariant">@android:color/system_outline_variant_dark</color>
-    <color name="settingslib_materialColorOnPrimary">@android:color/system_on_primary_dark</color>
-    <color name="settingslib_materialColorOnSurface">@android:color/system_on_surface_dark</color>
-    <color name="settingslib_materialColorSurfaceContainer">@android:color/system_surface_container_dark</color>
-    <color name="settingslib_materialColorPrimary">@android:color/system_primary_dark</color>
-    <color name="settingslib_materialColorSecondary">@android:color/system_secondary_dark</color>
-    <color name="settingslib_materialColorTertiary">@android:color/system_tertiary_dark</color>
 </resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml
new file mode 100644
index 0000000..e57fe4f
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-night/colors.xml
@@ -0,0 +1,53 @@
+<?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.
+-->
+
+<resources>
+    <color name="settingslib_materialColorPrimary">#83D6C7</color>
+    <color name="settingslib_materialColorOnPrimary">#003730</color>
+    <color name="settingslib_materialColorPrimaryContainer">#005047</color>
+    <color name="settingslib_materialColorOnPrimaryContainer">#A1F1E2</color>
+    <color name="settingslib_materialColorPrimaryInverse">#A1F1E2</color>
+    <color name="settingslib_materialColorSecondary">#B1CCC6</color>
+    <color name="settingslib_materialColorOnSecondary">#1C342F</color>
+    <color name="settingslib_materialColorSecondaryContainer">#334C47</color>
+    <color name="settingslib_materialColorOnSecondaryContainer">#CCE8E2</color>
+    <color name="settingslib_materialColorTertiary">#ADCAE5</color>
+    <color name="settingslib_materialColorOnTertiary">#123349</color>
+    <color name="settingslib_materialColorTertiaryContainer">#2D4960</color>
+    <color name="settingslib_materialColorOnTertiaryContainer">#CEE7FF</color>
+    <color name="settingslib_materialColorError">#F2B8B5</color>
+    <color name="settingslib_materialColorOnError">#601410</color>
+    <color name="settingslib_materialColorErrorContainer">#8C1D18</color>
+    <color name="settingslib_materialColorOnErrorContainer">#F9DEDC</color>
+    <color name="settingslib_materialColorOutline">#919191</color>
+    <color name="settingslib_materialColorOutlineVariant">#474747</color>
+    <color name="settingslib_materialColorBackground">#131313</color>
+    <color name="settingslib_materialColorOnBackground">#E5E2E1</color>
+    <color name="settingslib_materialColorSurface">#131313</color>
+    <color name="settingslib_materialColorOnSurface">#E5E2E1</color>
+    <color name="settingslib_materialColorSurfaceVariant">#474747</color>
+    <color name="settingslib_materialColorOnSurfaceVariant">#C7C7C7</color>
+    <color name="settingslib_materialColorSurfaceInverse">#E5E2E1</color>
+    <color name="settingslib_materialColorOnSurfaceInverse">#303030</color>
+    <color name="settingslib_materialColorSurfaceBright">#393939</color>
+    <color name="settingslib_materialColorSurfaceDim">#131313</color>
+    <color name="settingslib_materialColorSurfaceContainer">#1F1F1F</color>
+    <color name="settingslib_materialColorSurfaceContainerLowest">#1B1B1B</color>
+    <color name="settingslib_materialColorSurfaceContainerLow">#0E0E0E</color>
+    <color name="settingslib_materialColorSurfaceContainerHigh">#2A2A2A</color>
+    <color name="settingslib_materialColorSurfaceContainerHighest">#343434</color>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
index fef92b7..e000423 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
@@ -92,4 +92,51 @@
     <color name="settingslib_spinner_dropdown_color">@android:color/system_neutral2_700</color>
 
     <color name="settingslib_list_divider_color">@android:color/system_neutral1_200</color>
+
+    <color name="settingslib_materialColorPrimary">@android:color/system_accent1_600</color>
+    <color name="settingslib_materialColorOnPrimary">@android:color/system_accent1_0</color>
+    <color name="settingslib_materialColorPrimaryContainer">@android:color/system_accent1_100</color>
+    <color name="settingslib_materialColorOnPrimaryContainer">@android:color/system_accent1_900</color>
+    <color name="settingslib_materialColorPrimaryInverse">@android:color/system_accent1_200</color>
+    <color name="settingslib_materialColorPrimaryFixed">@android:color/system_accent1_100</color>
+    <color name="settingslib_materialColorPrimaryFixedDim">@android:color/system_accent1_200</color>
+    <color name="settingslib_materialColorOnPrimaryFixed">@android:color/system_accent1_900</color>
+    <color name="settingslib_materialColorOnPrimaryFixedVariant">@android:color/system_accent1_700</color>
+    <color name="settingslib_materialColorSecondary">@android:color/system_accent2_600</color>
+    <color name="settingslib_materialColorOnSecondary">@android:color/system_accent2_0</color>
+    <color name="settingslib_materialColorSecondaryContainer">@android:color/system_accent2_100</color>
+    <color name="settingslib_materialColorOnSecondaryContainer">@android:color/system_accent2_900</color>
+    <color name="settingslib_materialColorSecondaryFixed">@android:color/system_accent2_100</color>
+    <color name="settingslib_materialColorSecondaryFixedDim">@android:color/system_accent2_200</color>
+    <color name="settingslib_materialColorOnSecondaryFixed">@android:color/system_accent2_900</color>
+    <color name="settingslib_materialColorOnSecondaryFixedVariant">@android:color/system_accent2_700</color>
+    <color name="settingslib_materialColorTertiary">@android:color/system_accent3_600</color>
+    <color name="settingslib_materialColorOnTertiary">@android:color/system_accent3_0</color>
+    <color name="settingslib_materialColorTertiaryContainer">@android:color/system_accent3_100</color>
+    <color name="settingslib_materialColorOnTertiaryContainer">@android:color/system_accent3_900</color>
+    <color name="settingslib_materialColorTertiaryFixed">@android:color/system_accent3_100</color>
+    <color name="settingslib_materialColorTertiaryFixedDim">@android:color/system_accent3_200</color>
+    <color name="settingslib_materialColorOnTertiaryFixed">@android:color/system_accent3_900</color>
+    <color name="settingslib_materialColorOnTertiaryFixedVariant">@android:color/system_accent3_700</color>
+    <color name="settingslib_materialColorError">@color/settingslib_error_600</color>
+    <color name="settingslib_materialColorOnError">@android:color/white</color>
+    <color name="settingslib_materialColorErrorContainer">@color/settingslib_error_100</color>
+    <color name="settingslib_materialColorOnErrorContainer">@color/settingslib_error_900</color>
+    <color name="settingslib_materialColorOutline">@android:color/system_neutral2_500</color>
+    <color name="settingslib_materialColorOutlineVariant">@android:color/system_neutral2_200</color>
+    <color name="settingslib_materialColorBackground">@android:color/white</color>
+    <color name="settingslib_materialColorOnBackground">@android:color/system_neutral1_900</color>
+    <color name="settingslib_materialColorSurface">@color/settingslib_neutral_variant98</color>
+    <color name="settingslib_materialColorOnSurface">@android:color/system_neutral1_900</color>
+    <color name="settingslib_materialColorSurfaceVariant">@android:color/system_neutral2_100</color>
+    <color name="settingslib_materialColorOnSurfaceVariant">@android:color/system_neutral2_700</color>
+    <color name="settingslib_materialColorSurfaceInverse">@android:color/system_neutral1_800</color>
+    <color name="settingslib_materialColorOnSurfaceInverse">@android:color/system_neutral1_50</color>
+    <color name="settingslib_materialColorSurfaceBright">@color/settingslib_neutral_variant98</color>
+    <color name="settingslib_materialColorSurfaceDim">@color/settingslib_neutral_variant87</color>
+    <color name="settingslib_materialColorSurfaceContainer">@color/settingslib_neutral_variant94</color>
+    <color name="settingslib_materialColorSurfaceContainerLow">@color/settingslib_neutral_variant96</color>
+    <color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_neutral2_0</color>
+    <color name="settingslib_materialColorSurfaceContainerHigh">@color/settingslib_neutral_variant92</color>
+    <color name="settingslib_materialColorSurfaceContainerHighest">@android:color/system_neutral2_100</color>
 </resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/config.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/config.xml
index 4860ad3..8993d0f 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/config.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/config.xml
@@ -17,6 +17,4 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <bool name="settingslib_config_icon_space_reserved">false</bool>
     <bool name="settingslib_config_allow_divider">false</bool>
-    <!-- Name of a font family to use for headlines in SettingsLib. -->
-    <string name="settingslib_config_headlineFontFamily" translatable="false"></string>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml
new file mode 100644
index 0000000..9d3d70b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles_expressive.xml
@@ -0,0 +1,129 @@
+<?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.
+  -->
+
+<resources>
+    <style name="SettingsLibButtonStyle.Expressive.Filled"
+        parent="@style/Widget.Material3.Button">
+        <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:gravity">center</item>
+        <item name="android:minWidth">@dimen/settingslib_expressive_space_medium4</item>
+        <item name="android:minHeight">@dimen/settingslib_expressive_space_medium4</item>
+        <item name="android:paddingVertical">@dimen/settingslib_expressive_space_extrasmall5</item>
+        <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small1</item>
+        <item name="android:backgroundTint">@color/settingslib_materialColorPrimary</item>
+        <item name="android:textAppearance">@style/TextAppearance.SettingsLib.LabelLarge</item>
+        <item name="android:textColor">@color/settingslib_materialColorOnPrimary</item>
+        <item name="iconGravity">textStart</item>
+        <item name="iconTint">@color/settingslib_materialColorOnPrimary</item>
+        <item name="iconSize">@dimen/settingslib_expressive_space_small4</item>
+    </style>
+
+    <style name="SettingsLibButtonStyle.Expressive.Filled.Large">
+        <item name="android:paddingVertical">@dimen/settingslib_expressive_space_small1</item>
+        <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small4</item>
+        <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleMedium</item>
+    </style>
+
+    <style name="SettingsLibButtonStyle.Expressive.Filled.Extra"
+        parent="@style/SettingsLibButtonStyle.Expressive.Filled.Large">
+        <item name="android:layout_width">match_parent</item>
+    </style>
+
+    <style name="SettingsLibButtonStyle.Expressive.Tonal"
+        parent="@style/Widget.Material3.Button.TonalButton">
+        <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:gravity">center</item>
+        <item name="android:minWidth">@dimen/settingslib_expressive_space_medium4</item>
+        <item name="android:minHeight">@dimen/settingslib_expressive_space_medium4</item>
+        <item name="android:paddingVertical">@dimen/settingslib_expressive_space_extrasmall5</item>
+        <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small1</item>
+        <item name="android:backgroundTint">@color/settingslib_materialColorSecondaryContainer</item>
+        <item name="android:textAppearance">@style/TextAppearance.SettingsLib.LabelLarge</item>
+        <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item>
+        <item name="iconGravity">textStart</item>
+        <item name="iconTint">@color/settingslib_materialColorOnSecondaryContainer</item>
+        <item name="iconSize">@dimen/settingslib_expressive_space_small4</item>
+    </style>
+
+    <style name="SettingsLibButtonStyle.Expressive.Tonal.Large">
+        <item name="android:paddingVertical">@dimen/settingslib_expressive_space_small1</item>
+        <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small4</item>
+        <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleMedium</item>
+    </style>
+
+    <style name="SettingsLibButtonStyle.Expressive.Tonal.Extra"
+        parent="@style/SettingsLibButtonStyle.Expressive.Tonal.Large">
+        <item name="android:layout_width">match_parent</item>
+    </style>
+
+    <style name="SettingsLibButtonStyle.Expressive.Outline"
+        parent="@style/Widget.Material3.Button.OutlinedButton.Icon">
+        <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:gravity">center</item>
+        <item name="android:minWidth">@dimen/settingslib_expressive_space_medium4</item>
+        <item name="android:minHeight">@dimen/settingslib_expressive_space_medium4</item>
+        <item name="android:paddingVertical">@dimen/settingslib_expressive_space_extrasmall5</item>
+        <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small1</item>
+        <item name="android:textAppearance">@style/TextAppearance.SettingsLib.LabelLarge</item>
+        <item name="android:textColor">@color/settingslib_materialColorPrimary</item>
+        <item name="iconTint">@color/settingslib_materialColorPrimary</item>
+        <item name="iconGravity">textStart</item>
+        <item name="iconSize">@dimen/settingslib_expressive_space_small4</item>
+        <item name="iconPadding">@dimen/settingslib_expressive_space_extrasmall4</item>
+        <item name="strokeColor">@color/settingslib_materialColorOutlineVariant</item>
+    </style>
+
+    <style name="SettingsLibButtonStyle.Expressive.Outline.Large">
+        <item name="android:paddingVertical">@dimen/settingslib_expressive_space_small1</item>
+        <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small4</item>
+        <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleMedium</item>
+    </style>
+
+    <style name="SettingsLibButtonStyle.Expressive.Outline.Extra"
+        parent="@style/SettingsLibButtonStyle.Expressive.Outline.Large">
+        <item name="android:layout_width">match_parent</item>
+    </style>
+
+    <style name="SettingslibTextButtonStyle.Expressive"
+        parent="@style/Widget.Material3.Button.TextButton.Icon">
+        <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textAppearance">@style/TextAppearance.SettingsLib.BodyLarge.Emphasized</item>
+        <item name="android:textColor">@color/settingslib_materialColorOnSurface</item>
+        <item name="iconTint">@null</item>
+        <item name="iconPadding">@dimen/settingslib_expressive_space_extrasmall4</item>
+        <item name="rippleColor">?android:attr/colorControlHighlight</item>
+    </style>
+
+    <style name="SettingsLibCardStyle" parent="">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginHorizontal">?android:attr/listPreferredItemPaddingStart</item>
+        <item name="android:layout_marginVertical">@dimen/settingslib_expressive_space_extrasmall4</item>
+        <item name="cardBackgroundColor">@color/settingslib_materialColorPrimary</item>
+        <item name="cardCornerRadius">@dimen/settingslib_expressive_radius_extralarge3</item>
+        <item name="cardElevation">0dp</item>
+        <item name="rippleColor">?android:attr/colorControlHighlight</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v33/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v33/styles_expressive.xml
new file mode 100644
index 0000000..74bf55a
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v33/styles_expressive.xml
@@ -0,0 +1,306 @@
+<?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.
+  -->
+
+<resources>
+    <style name="TextAppearance.SettingsLib.DisplayLarge"
+        parent="@android:style/TextAppearance.DeviceDefault.Headline">
+        <item name="android:textSize">57sp</item>
+        <item name="android:letterSpacing">-0.00438596</item>
+        <item name="android:lineHeight">64sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.DisplayMedium"
+        parent="@android:style/TextAppearance.DeviceDefault.Headline">
+        <item name="android:textSize">45sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight">52sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.DisplaySmall"
+        parent="@android:style/TextAppearance.DeviceDefault.Headline">
+        <item name="android:textSize">36sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight">44sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.HeadlineLarge"
+        parent="@android:style/TextAppearance.DeviceDefault.Headline">
+        <item name="android:textSize">32sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight">40sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.HeadlineMedium"
+        parent="@android:style/TextAppearance.DeviceDefault.Headline">
+        <item name="android:textSize">28sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight">36sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.HeadlineSmall"
+        parent="@android:style/TextAppearance.DeviceDefault.Headline">
+        <item name="android:textSize">24sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight">32sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.TitleLarge"
+        parent="@android:style/TextAppearance.DeviceDefault.Headline">
+        <item name="android:textSize">22sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight">28sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.TitleMedium"
+        parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textSize">16sp</item>
+        <item name="android:letterSpacing">0.009375</item>
+        <item name="android:lineHeight">24sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.TitleSmall"
+        parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textSize">14sp</item>
+        <item name="android:letterSpacing">0.00714286</item>
+        <item name="android:lineHeight">20sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.LabelLarge"
+        parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textSize">14sp</item>
+        <item name="android:letterSpacing">0.00714286</item>
+        <item name="android:lineHeight">20sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.LabelMedium"
+        parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textSize">12sp</item>
+        <item name="android:letterSpacing">0.04166667</item>
+        <item name="android:lineHeight">16sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.LabelSmall"
+        parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textSize">11sp</item>
+        <item name="android:letterSpacing">0.04545455</item>
+        <item name="android:lineHeight">16sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.BodyLarge"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textSize">16sp</item>
+        <item name="android:letterSpacing">0.03125</item>
+        <item name="android:lineHeight">24sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.BodyMedium"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textSize">14sp</item>
+        <item name="android:letterSpacing">0.01785714</item>
+        <item name="android:lineHeight">20sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.BodySmall"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textSize">12sp</item>
+        <item name="android:letterSpacing">0.03333333</item>
+        <item name="android:lineHeight">16sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.DisplayLarge.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:textSize">57sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight">64sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.DisplayMedium.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:textSize">45sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight">52sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.DisplaySmall.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:textSize">36sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight">44sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.HeadlineLarge.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:textSize">32sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight">40sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.HeadlineMedium.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:textSize">28sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight">36sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.HeadlineSmall.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:textSize">24sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight">32sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.TitleLarge.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:textSize">22sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight">28sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.TitleMedium.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textStyle">bold</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:letterSpacing">0.009375</item>
+        <item name="android:lineHeight">24sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.TitleSmall.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textStyle">bold</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:letterSpacing">0.00714286</item>
+        <item name="android:lineHeight">20sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.LabelLarge.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textStyle">bold</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:letterSpacing">0.00714286</item>
+        <item name="android:lineHeight">20sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.LabelMedium.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textStyle">bold</item>
+        <item name="android:textSize">12sp</item>
+        <item name="android:letterSpacing">0.04166667</item>
+        <item name="android:lineHeight">16sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.LabelSmall.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textStyle">bold</item>
+        <item name="android:textSize">11sp</item>
+        <item name="android:letterSpacing">0.04545455</item>
+        <item name="android:lineHeight">16sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.BodyLarge.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:letterSpacing">0.009375</item>
+        <item name="android:lineHeight">24sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.BodyMedium.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:letterSpacing">0.01785714</item>
+        <item name="android:lineHeight">20sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.BodySmall.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">12sp</item>
+        <item name="android:letterSpacing">0.03333333</item>
+        <item name="android:lineHeight">16sp</item>
+        <item name="android:hyphenationFrequency">normalFast</item>
+        <item name="android:lineBreakWordStyle">phrase</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml
index 185ac3e..60642e6 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v34/colors.xml
@@ -43,4 +43,51 @@
     <color name="settingslib_text_color_primary_device_default">@android:color/system_on_surface_light</color>
     <!--Deprecated. After sdk 35 don't use it. using materialColorOnSurfaceVariant-->
     <color name="settingslib_text_color_secondary_device_default">@android:color/system_on_surface_variant_light</color>
+
+    <color name="settingslib_materialColorPrimary">@android:color/system_primary_light</color>
+    <color name="settingslib_materialColorOnPrimary">@android:color/system_on_primary_light</color>
+    <color name="settingslib_materialColorPrimaryContainer">@android:color/system_primary_container_light</color>
+    <color name="settingslib_materialColorOnPrimaryContainer">@android:color/system_on_primary_container_light</color>
+    <color name="settingslib_materialColorPrimaryInverse">@android:color/system_primary_dark</color>
+    <color name="settingslib_materialColorPrimaryFixed">@android:color/system_primary_fixed</color>
+    <color name="settingslib_materialColorPrimaryFixedDim">@android:color/system_primary_fixed_dim</color>
+    <color name="settingslib_materialColorOnPrimaryFixed">@android:color/system_on_primary_fixed</color>
+    <color name="settingslib_materialColorOnPrimaryFixedVariant">@android:color/system_on_primary_fixed_variant</color>
+    <color name="settingslib_materialColorSecondary">@android:color/system_secondary_light</color>
+    <color name="settingslib_materialColorOnSecondary">@android:color/system_on_secondary_light</color>
+    <color name="settingslib_materialColorSecondaryContainer">@android:color/system_secondary_container_light</color>
+    <color name="settingslib_materialColorOnSecondaryContainer">@android:color/system_on_secondary_container_light</color>
+    <color name="settingslib_materialColorSecondaryFixed">@android:color/system_secondary_fixed</color>
+    <color name="settingslib_materialColorSecondaryFixedDim">@android:color/system_secondary_fixed_dim</color>
+    <color name="settingslib_materialColorOnSecondaryFixed">@android:color/system_on_secondary_fixed</color>
+    <color name="settingslib_materialColorOnSecondaryFixedVariant">@android:color/system_on_secondary_fixed_variant</color>
+    <color name="settingslib_materialColorTertiary">@android:color/system_tertiary_light</color>
+    <color name="settingslib_materialColorOnTertiary">@android:color/system_on_tertiary_light</color>
+    <color name="settingslib_materialColorTertiaryContainer">@android:color/system_tertiary_container_light</color>
+    <color name="settingslib_materialColorOnTertiaryContainer">@android:color/system_on_tertiary_container_light</color>
+    <color name="settingslib_materialColorTertiaryFixed">@android:color/system_tertiary_fixed</color>
+    <color name="settingslib_materialColorTertiaryFixedDim">@android:color/system_tertiary_fixed_dim</color>
+    <color name="settingslib_materialColorOnTertiaryFixed">@android:color/system_on_tertiary_fixed</color>
+    <color name="settingslib_materialColorOnTertiaryFixedVariant">@android:color/system_on_tertiary_fixed_variant</color>
+    <color name="settingslib_materialColorError">@android:color/system_error_light</color>
+    <color name="settingslib_materialColorOnError">@android:color/system_on_error_light</color>
+    <color name="settingslib_materialColorErrorContainer">@android:color/system_error_container_light</color>
+    <color name="settingslib_materialColorOnErrorContainer">@android:color/system_on_error_container_light</color>
+    <color name="settingslib_materialColorOutline">@android:color/system_outline_light</color>
+    <color name="settingslib_materialColorOutlineVariant">@android:color/system_outline_variant_light</color>
+    <color name="settingslib_materialColorBackground">@android:color/system_background_light</color>
+    <color name="settingslib_materialColorOnBackground">@android:color/system_on_background_light</color>
+    <color name="settingslib_materialColorSurface">@android:color/system_surface_light</color>
+    <color name="settingslib_materialColorOnSurface">@android:color/system_on_surface_light</color>
+    <color name="settingslib_materialColorSurfaceVariant">@android:color/system_surface_variant_light</color>
+    <color name="settingslib_materialColorOnSurfaceVariant">@android:color/system_on_surface_variant_light</color>
+    <color name="settingslib_materialColorSurfaceInverse">@android:color/system_surface_dark</color>
+    <color name="settingslib_materialColorOnSurfaceInverse">@android:color/system_on_surface_dark</color>
+    <color name="settingslib_materialColorSurfaceBright">@android:color/system_surface_bright_light</color>
+    <color name="settingslib_materialColorSurfaceDim">@android:color/system_surface_dim_light</color>
+    <color name="settingslib_materialColorSurfaceContainer">@android:color/system_surface_container_light</color>
+    <color name="settingslib_materialColorSurfaceContainerLow">@android:color/system_surface_container_low_light</color>
+    <color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_light</color>
+    <color name="settingslib_materialColorSurfaceContainerHigh">@android:color/system_surface_container_high_light</color>
+    <color name="settingslib_materialColorSurfaceContainerHighest">@android:color/system_surface_container_highest_light</color>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
index 90c19e1..b1b37b1 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml
@@ -54,49 +54,4 @@
     <color name="settingslib_spinner_title_color">@color/settingslib_materialColorOnPrimaryContainer</color>
     <!-- The text color of dropdown item title -->
     <color name="settingslib_spinner_dropdown_color">@color/settingslib_materialColorOnPrimaryContainer</color>
-
-    <color name="settingslib_materialColorOnSecondaryFixedVariant">@android:color/system_on_secondary_fixed_variant</color>
-    <color name="settingslib_materialColorOnTertiaryFixedVariant">@android:color/system_on_tertiary_fixed_variant</color>
-    <color name="settingslib_materialColorSurfaceContainerLowest">@android:color/system_surface_container_lowest_light</color>
-    <color name="settingslib_materialColorOnPrimaryFixedVariant">@android:color/system_on_primary_fixed_variant</color>
-    <color name="settingslib_materialColorOnSecondaryContainer">@android:color/system_on_secondary_container_light</color>
-    <color name="settingslib_materialColorOnTertiaryContainer">@android:color/system_on_tertiary_container_light</color>
-    <color name="settingslib_materialColorSurfaceContainerLow">@android:color/system_surface_container_low_light</color>
-    <color name="settingslib_materialColorOnPrimaryContainer">@android:color/system_on_primary_container_light</color>
-    <color name="settingslib_materialColorSecondaryFixedDim">@android:color/system_secondary_fixed_dim</color>
-    <color name="settingslib_materialColorOnErrorContainer">@android:color/system_on_error_container_light</color>
-    <color name="settingslib_materialColorOnSecondaryFixed">@android:color/system_on_secondary_fixed</color>
-    <color name="settingslib_materialColorInverseOnSurface">@android:color/system_on_surface_dark</color>
-    <color name="settingslib_materialColorTertiaryFixedDim">@android:color/system_tertiary_fixed_dim</color>
-    <color name="settingslib_materialColorOnTertiaryFixed">@android:color/system_on_tertiary_fixed</color>
-    <color name="settingslib_materialColorPrimaryFixedDim">@android:color/system_primary_fixed_dim</color>
-    <color name="settingslib_materialColorSecondaryContainer">@android:color/system_secondary_container_light</color>
-    <color name="settingslib_materialColorErrorContainer">@android:color/system_error_container_light</color>
-    <color name="settingslib_materialColorOnPrimaryFixed">@android:color/system_on_primary_fixed</color>
-    <color name="settingslib_materialColorInversePrimary">@android:color/system_primary_dark</color>
-    <color name="settingslib_materialColorSecondaryFixed">@android:color/system_secondary_fixed</color>
-    <color name="settingslib_materialColorInverseSurface">@android:color/system_surface_dark</color>
-    <color name="settingslib_materialColorSurfaceVariant">@android:color/system_surface_variant_light</color>
-    <color name="settingslib_materialColorTertiaryContainer">@android:color/system_tertiary_container_light</color>
-    <color name="settingslib_materialColorTertiaryFixed">@android:color/system_tertiary_fixed</color>
-    <color name="settingslib_materialColorPrimaryContainer">@android:color/system_primary_container_light</color>
-    <color name="settingslib_materialColorOnBackground">@android:color/system_on_background_light</color>
-    <color name="settingslib_materialColorPrimaryFixed">@android:color/system_primary_fixed</color>
-    <color name="settingslib_materialColorOnSecondary">@android:color/system_on_secondary_light</color>
-    <color name="settingslib_materialColorOnTertiary">@android:color/system_on_tertiary_light</color>
-    <color name="settingslib_materialColorSurfaceDim">@android:color/system_surface_dim_light</color>
-    <color name="settingslib_materialColorSurfaceBright">@android:color/system_surface_bright_light</color>
-    <color name="settingslib_materialColorOnError">@android:color/system_on_error_light</color>
-    <color name="settingslib_materialColorSurface">@android:color/system_surface_light</color>
-    <color name="settingslib_materialColorSurfaceContainerHigh">@android:color/system_surface_container_high_light</color>
-    <color name="settingslib_materialColorSurfaceContainerHighest">@android:color/system_surface_container_highest_light</color>
-    <color name="settingslib_materialColorOnSurfaceVariant">@android:color/system_on_surface_variant_light</color>
-    <color name="settingslib_materialColorOutline">@android:color/system_outline_light</color>
-    <color name="settingslib_materialColorOutlineVariant">@android:color/system_outline_variant_light</color>
-    <color name="settingslib_materialColorOnPrimary">@android:color/system_on_primary_light</color>
-    <color name="settingslib_materialColorOnSurface">@android:color/system_on_surface_light</color>
-    <color name="settingslib_materialColorSurfaceContainer">@android:color/system_surface_container_light</color>
-    <color name="settingslib_materialColorPrimary">@android:color/system_primary_light</color>
-    <color name="settingslib_materialColorSecondary">@android:color/system_secondary_light</color>
-    <color name="settingslib_materialColorTertiary">@android:color/system_tertiary_light</color>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
index 05a1cea..1a08568 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
@@ -16,150 +16,6 @@
   -->
 
 <resources>
-    <style name="SettingsLibTextAppearance" parent="@android:style/TextAppearance.DeviceDefault">
-        <!--item name="android:fontFamily"></item-->
-        <item name="android:hyphenationFrequency">normalFast</item>
-        <item name="android:lineBreakWordStyle">phrase</item>
-    </style>
-
-    <style name="SettingsLibTextAppearance.Primary">
-        <!--item name="android:fontFamily"></item-->
-    </style>
-
-    <style name="SettingsLibTextAppearance.Primary.Display">
-        <!--item name="android:fontFamily"></item-->
-    </style>
-    <style name="SettingsLibTextAppearance.Primary.Display.Large">
-        <item name="android:textSize">57sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Primary.Display.Medium">
-        <item name="android:textSize">45sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Primary.Display.Small">
-        <item name="android:textSize">36sp</item>
-    </style>
-
-    <style name="SettingsLibTextAppearance.Primary.Headline">
-        <!--item name="android:fontFamily"></item-->
-    </style>
-    <style name="SettingsLibTextAppearance.Primary.Headline.Large">
-        <item name="android:textSize">32sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Primary.Headline.Medium">
-        <item name="android:textSize">28sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Primary.Headline.Small">
-        <item name="android:textSize">24sp</item>
-    </style>
-
-    <style name="SettingsLibTextAppearance.Primary.Title">
-        <!--item name="android:fontFamily"></item-->
-    </style>
-    <style name="SettingsLibTextAppearance.Primary.Title.Large">
-        <item name="android:textSize">22sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Primary.Title.Medium">
-        <item name="android:textSize">16sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Primary.Title.Small">
-        <item name="android:textSize">14sp</item>
-    </style>
-
-    <style name="SettingsLibTextAppearance.Primary.Label">
-        <!--item name="android:fontFamily"></item-->
-    </style>
-    <style name="SettingsLibTextAppearance.Primary.Label.Large">
-        <item name="android:textSize">14sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Primary.Label.Medium">
-        <item name="android:textSize">12sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Primary.Label.Small">
-        <item name="android:textSize">11sp</item>
-    </style>
-
-    <style name="SettingsLibTextAppearance.Primary.Body">
-        <!--item name="android:fontFamily"></item-->
-    </style>
-    <style name="SettingsLibTextAppearance.Primary.Body.Large">
-        <item name="android:textSize">16sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Primary.Body.Medium">
-        <item name="android:textSize">14sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Primary.Body.Small">
-        <item name="android:textSize">12sp</item>
-    </style>
-
-    <style name="SettingsLibTextAppearance.Emphasized">
-        <!--item name="android:fontFamily"></item-->
-    </style>
-
-    <style name="SettingsLibTextAppearance.Emphasized.Display">
-        <!--item name="android:fontFamily"></item-->
-    </style>
-    <style name="SettingsLibTextAppearance.Emphasized.Display.Large">
-        <item name="android:textSize">57sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Emphasized.Display.Medium">
-        <item name="android:textSize">45sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Emphasized.Display.Small">
-        <item name="android:textSize">36sp</item>
-    </style>
-
-    <style name="SettingsLibTextAppearance.Emphasized.Headline">
-        <!--item name="android:fontFamily"></item-->
-    </style>
-    <style name="SettingsLibTextAppearance.Emphasized.Headline.Large">
-        <item name="android:textSize">32sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Emphasized.Headline.Medium">
-        <item name="android:textSize">28sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Emphasized.Headline.Small">
-        <item name="android:textSize">24sp</item>
-    </style>
-
-    <style name="SettingsLibTextAppearance.Emphasized.Title">
-        <!--item name="android:fontFamily"></item-->
-    </style>
-    <style name="SettingsLibTextAppearance.Emphasized.Title.Large">
-        <item name="android:textSize">22sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Emphasized.Title.Medium">
-        <item name="android:textSize">16sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Emphasized.Title.Small">
-        <item name="android:textSize">14sp</item>
-    </style>
-
-    <style name="SettingsLibTextAppearance.Emphasized.Label">
-        <!--item name="android:fontFamily"></item-->
-    </style>
-    <style name="SettingsLibTextAppearance.Emphasized.Label.Large">
-        <item name="android:textSize">14sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Emphasized.Label.Medium">
-        <item name="android:textSize">12sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Emphasized.Label.Small">
-        <item name="android:textSize">11sp</item>
-    </style>
-
-    <style name="SettingsLibTextAppearance.Emphasized.Body">
-        <!--item name="android:fontFamily"></item-->
-    </style>
-    <style name="SettingsLibTextAppearance.Emphasized.Body.Large">
-        <item name="android:textSize">16sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Emphasized.Body.Medium">
-        <item name="android:textSize">14sp</item>
-    </style>
-    <style name="SettingsLibTextAppearance.Emphasized.Body.Small">
-        <item name="android:textSize">12sp</item>
-    </style>
-
     <style name="SettingslibSwitchStyle.Expressive" parent="">
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">wrap_content</item>
@@ -175,122 +31,6 @@
         <item name="trackTint">@color/settingslib_expressive_color_main_switch_track</item>
     </style>
 
-    <style name="SettingsLibCardStyle" parent="">
-        <item name="android:layout_width">match_parent</item>
-        <item name="android:layout_height">wrap_content</item>
-        <item name="android:layout_marginHorizontal">?android:attr/listPreferredItemPaddingStart</item>
-        <item name="android:layout_marginVertical">@dimen/settingslib_expressive_space_extrasmall4</item>
-        <item name="cardBackgroundColor">@color/settingslib_materialColorPrimary</item>
-        <item name="cardCornerRadius">@dimen/settingslib_expressive_radius_extralarge3</item>
-        <item name="cardElevation">0dp</item>
-        <item name="rippleColor">?android:attr/colorControlHighlight</item>
-    </style>
-
-    <style name="SettingsLibButtonStyle.Expressive.Filled"
-        parent="@style/Widget.Material3.Button">
-        <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
-        <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">wrap_content</item>
-        <item name="android:gravity">center</item>
-        <item name="android:minWidth">@dimen/settingslib_expressive_space_medium4</item>
-        <item name="android:minHeight">@dimen/settingslib_expressive_space_medium4</item>
-        <item name="android:paddingVertical">@dimen/settingslib_expressive_space_extrasmall5</item>
-        <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small1</item>
-        <item name="android:backgroundTint">@color/settingslib_materialColorPrimary</item>
-        <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
-        <item name="android:textColor">@color/settingslib_materialColorOnPrimary</item>
-        <item name="android:textSize">14sp</item>
-        <item name="iconGravity">textStart</item>
-        <item name="iconTint">@color/settingslib_materialColorOnPrimary</item>
-        <item name="iconSize">@dimen/settingslib_expressive_space_small4</item>
-    </style>
-
-    <style name="SettingsLibButtonStyle.Expressive.Filled.Large">
-        <item name="android:paddingVertical">@dimen/settingslib_expressive_space_small1</item>
-        <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small4</item>
-        <item name="android:textSize">16sp</item>
-    </style>
-
-    <style name="SettingsLibButtonStyle.Expressive.Filled.Extra"
-        parent="@style/SettingsLibButtonStyle.Expressive.Filled.Large">
-        <item name="android:layout_width">match_parent</item>
-    </style>
-
-    <style name="SettingsLibButtonStyle.Expressive.Tonal"
-        parent="@style/Widget.Material3.Button.TonalButton">
-        <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
-        <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">wrap_content</item>
-        <item name="android:gravity">center</item>
-        <item name="android:minWidth">@dimen/settingslib_expressive_space_medium4</item>
-        <item name="android:minHeight">@dimen/settingslib_expressive_space_medium4</item>
-        <item name="android:paddingVertical">@dimen/settingslib_expressive_space_extrasmall5</item>
-        <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small1</item>
-        <item name="android:backgroundTint">@color/settingslib_materialColorSecondaryContainer</item>
-        <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
-        <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item>
-        <item name="android:textSize">14sp</item>
-        <item name="iconGravity">textStart</item>
-        <item name="iconTint">@color/settingslib_materialColorOnSecondaryContainer</item>
-        <item name="iconSize">@dimen/settingslib_expressive_space_small4</item>
-    </style>
-
-    <style name="SettingsLibButtonStyle.Expressive.Tonal.Large">
-        <item name="android:paddingVertical">@dimen/settingslib_expressive_space_small1</item>
-        <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small4</item>
-        <item name="android:textSize">16sp</item>
-    </style>
-
-    <style name="SettingsLibButtonStyle.Expressive.Tonal.Extra"
-        parent="@style/SettingsLibButtonStyle.Expressive.Tonal.Large">
-        <item name="android:layout_width">match_parent</item>
-    </style>
-
-    <style name="SettingsLibButtonStyle.Expressive.Outline"
-        parent="@style/Widget.Material3.Button.OutlinedButton.Icon">
-        <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
-        <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">wrap_content</item>
-        <item name="android:gravity">center</item>
-        <item name="android:minWidth">@dimen/settingslib_expressive_space_medium4</item>
-        <item name="android:minHeight">@dimen/settingslib_expressive_space_medium4</item>
-        <item name="android:paddingVertical">@dimen/settingslib_expressive_space_extrasmall5</item>
-        <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small1</item>
-        <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
-        <item name="android:textColor">@color/settingslib_materialColorPrimary</item>
-        <item name="android:textSize">14sp</item>
-        <item name="iconTint">@color/settingslib_materialColorPrimary</item>
-        <item name="iconGravity">textStart</item>
-        <item name="iconSize">@dimen/settingslib_expressive_space_small4</item>
-        <item name="iconPadding">@dimen/settingslib_expressive_space_extrasmall4</item>
-        <item name="strokeColor">@color/settingslib_materialColorOutlineVariant</item>
-
-    </style>
-
-    <style name="SettingsLibButtonStyle.Expressive.Outline.Large">
-        <item name="android:paddingVertical">@dimen/settingslib_expressive_space_small1</item>
-        <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small4</item>
-        <item name="android:textSize">16sp</item>
-    </style>
-
-    <style name="SettingsLibButtonStyle.Expressive.Outline.Extra"
-        parent="@style/SettingsLibButtonStyle.Expressive.Outline.Large">
-        <item name="android:layout_width">match_parent</item>
-    </style>
-
-    <style name="SettingslibTextButtonStyle.Expressive"
-        parent="@style/Widget.Material3.Button.TextButton.Icon">
-        <item name="android:theme">@style/Theme.Material3.DynamicColors.DayNight</item>
-        <item name="android:layout_width">wrap_content</item>
-        <item name="android:layout_height">wrap_content</item>
-        <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item>
-        <item name="android:textSize">16sp</item>
-        <item name="android:textColor">@color/settingslib_materialColorOnSurface</item>
-        <item name="iconTint">@null</item>
-        <item name="iconPadding">@dimen/settingslib_expressive_space_extrasmall4</item>
-        <item name="rippleColor">?android:attr/colorControlHighlight</item>
-    </style>
-
     <style name="EntityHeader">
         <item name="android:paddingTop">@dimen/settingslib_expressive_space_small4</item>
         <item name="android:paddingBottom">@dimen/settingslib_expressive_space_small1</item>
@@ -327,12 +67,11 @@
         <item name="android:gravity">center</item>
         <item name="android:ellipsize">marquee</item>
         <item name="android:textDirection">locale</item>
-        <item name="android:textAppearance">@style/TextAppearance.EntityHeaderTitle</item>
+        <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleLarge.Emphasized</item>
     </style>
 
     <style name="SettingslibTextAppearance.LinkableTextStyle.Expressive"
-           parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
-        <item name="android:textSize">14sp</item>
+           parent="@style/TextAppearance.SettingsLib.LabelLarge">
         <item name="android:textColor">?android:attr/colorAccent</item>
     </style>
 
@@ -346,4 +85,14 @@
         <item name="cardElevation">0dp</item>
         <item name="rippleColor">?android:attr/colorControlHighlight</item>
     </style>
+
+    <style name="TextAppearance.SettingsLib.PreferenceTitle"
+        parent="@style/TextAppearance.SettingsLib.TitleMedium">
+        <item name="android:textColor">@color/settingslib_text_color_primary</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.PreferenceSummary"
+        parent="@style/TextAppearance.SettingsLib.BodyMedium">
+        <item name="android:textColor">@color/settingslib_text_color_secondary</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes_expressive.xml
index fea8739..14f214a 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes_expressive.xml
@@ -18,8 +18,8 @@
 <resources>
     <style name="Theme.SettingsBase.Expressive">
         <!-- Set up Preference title text style -->
-        <!--item name="android:textAppearanceListItem">@style/TextAppearance.PreferenceTitle.SettingsLib</item-->
-        <!--item name="android:textAppearanceListItemSecondary">@style/textAppearanceListItemSecondary</item-->
+        <item name="android:textAppearanceListItem">@style/TextAppearance.SettingsLib.PreferenceTitle</item>
+        <item name="android:textAppearanceListItemSecondary">@style/TextAppearance.SettingsLib.PreferenceSummary</item>
 
         <!-- Set up  list item padding -->
         <item name="android:listPreferredItemPaddingStart">@dimen/settingslib_expressive_space_small1</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/attrs_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values/attrs_expressive.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/values-v35/attrs_expressive.xml
rename to packages/SettingsLib/SettingsTheme/res/values/attrs_expressive.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/values/colors.xml b/packages/SettingsLib/SettingsTheme/res/values/colors.xml
new file mode 100644
index 0000000..c5c613b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values/colors.xml
@@ -0,0 +1,79 @@
+<?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.
+-->
+
+<resources>
+    <color name="settingslib_error_0">#FFFFFF</color>
+    <color name="settingslib_error_10">#FFFBFF</color>
+    <color name="settingslib_error_50">#FFEDEA</color>
+    <color name="settingslib_error_100">#FFDAD6</color>
+    <color name="settingslib_error_200">#FFB4AB</color>
+    <color name="settingslib_error_300">#FF897D</color>
+    <color name="settingslib_error_400">#FF5449</color>
+    <color name="settingslib_error_500">#DE3730</color>
+    <color name="settingslib_error_600">#BA1A1A</color>
+    <color name="settingslib_error_700">#93000A</color>
+    <color name="settingslib_error_800">#690005</color>
+    <color name="settingslib_error_900">#410002</color>
+    <color name="settingslib_error_1000">#000000</color>
+
+    <color name="settingslib_materialColorPrimary">#006B5F</color>
+    <color name="settingslib_materialColorOnPrimary">#FFFFFF</color>
+    <color name="settingslib_materialColorPrimaryContainer">#C5EAE2</color>
+    <color name="settingslib_materialColorOnPrimaryContainer">#00201C</color>
+    <color name="settingslib_materialColorPrimaryInverse">#83D6C7</color>
+    <color name="settingslib_materialColorPrimaryFixed">#C5EAE2</color>
+    <color name="settingslib_materialColorPrimaryFixedDim">#82D5C6</color>
+    <color name="settingslib_materialColorOnPrimaryFixed">#00201C</color>
+    <color name="settingslib_materialColorOnPrimaryFixedVariant">#005047</color>
+    <color name="settingslib_materialColorSecondary">#4A635E</color>
+    <color name="settingslib_materialColorOnSecondary">#FFFFFF</color>
+    <color name="settingslib_materialColorSecondaryContainer">#CCE8E2</color>
+    <color name="settingslib_materialColorOnSecondaryContainer">#051F1B</color>
+    <color name="settingslib_materialColorSecondaryFixed">#CCE8E2</color>
+    <color name="settingslib_materialColorSecondaryFixedDim">#B1CCC6</color>
+    <color name="settingslib_materialColorOnSecondaryFixed">#051F1B</color>
+    <color name="settingslib_materialColorOnSecondaryFixedVariant">#334C47</color>
+    <color name="settingslib_materialColorTertiary">#456179</color>
+    <color name="settingslib_materialColorOnTertiary">#FFFFFF</color>
+    <color name="settingslib_materialColorTertiaryContainer">#CBE6FF</color>
+    <color name="settingslib_materialColorOnTertiaryContainer">#001E31</color>
+    <color name="settingslib_materialColorTertiaryFixed">#CBE5FF</color>
+    <color name="settingslib_materialColorTertiaryFixedDim">#ADCAE5</color>
+    <color name="settingslib_materialColorOnTertiaryFixed">#001E31</color>
+    <color name="settingslib_materialColorOnTertiaryFixedVariant">#2D4A60</color>
+    <color name="settingslib_materialColorError">#B3261E</color>
+    <color name="settingslib_materialColorOnError">#FFFFFF</color>
+    <color name="settingslib_materialColorErrorContainer">#F9DEDC</color>
+    <color name="settingslib_materialColorOnErrorContainer">#3A0A08</color>
+    <color name="settingslib_materialColorOutline">#777777</color>
+    <color name="settingslib_materialColorOutlineVariant">#C7C6C5</color>
+    <color name="settingslib_materialColorBackground">#F9FAF8</color>
+    <color name="settingslib_materialColorOnBackground">#1B1B1B</color>
+    <color name="settingslib_materialColorSurface">#F9FAF8</color>
+    <color name="settingslib_materialColorOnSurface">#1B1B1B</color>
+    <color name="settingslib_materialColorSurfaceVariant">#E3E3E3</color>
+    <color name="settingslib_materialColorOnSurfaceVariant">#474747</color>
+    <color name="settingslib_materialColorSurfaceInverse">#303030</color>
+    <color name="settingslib_materialColorOnSurfaceInverse">#F1F1F1</color>
+    <color name="settingslib_materialColorSurfaceBright">#F9FAF8</color>
+    <color name="settingslib_materialColorSurfaceDim">#DADADA</color>
+    <color name="settingslib_materialColorSurfaceContainer">#EEEEEE</color>
+    <color name="settingslib_materialColorSurfaceContainerLow">#F4F4F4</color>
+    <color name="settingslib_materialColorSurfaceContainerLowest">#FFFFFF</color>
+    <color name="settingslib_materialColorSurfaceContainerHigh">#E8E8E8</color>
+    <color name="settingslib_materialColorSurfaceContainerHighest">#E3E3E3</color>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values/config.xml b/packages/SettingsLib/SettingsTheme/res/values/config.xml
index e73dcc0..53da491 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/config.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/config.xml
@@ -16,4 +16,7 @@
   -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <bool name="settingslib_config_icon_space_reserved">true</bool>
+
+    <!-- Name of a font family to use for headlines in SettingsLib. -->
+    <string name="settingslib_config_headlineFontFamily" translatable="false"></string>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/dimens_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values/dimens_expressive.xml
similarity index 100%
rename from packages/SettingsLib/SettingsTheme/res/values-v35/dimens_expressive.xml
rename to packages/SettingsLib/SettingsTheme/res/values/dimens_expressive.xml
diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values/styles_expressive.xml
new file mode 100644
index 0000000..f73e100
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values/styles_expressive.xml
@@ -0,0 +1,253 @@
+<?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.
+  -->
+
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <style name="TextAppearance.SettingsLib.DisplayLarge"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
+        <item name="android:textSize">57sp</item>
+        <item name="android:letterSpacing">-0.00438596</item>
+        <item name="android:lineHeight" tools:targetApi="28">64sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.DisplayMedium"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
+        <item name="android:textSize">45sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight" tools:targetApi="28">52sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.DisplaySmall"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
+        <item name="android:textSize">36sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight" tools:targetApi="28">44sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.HeadlineLarge"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
+        <item name="android:textSize">32sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight" tools:targetApi="28">40sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.HeadlineMedium"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
+        <item name="android:textSize">28sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight" tools:targetApi="28">36sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.HeadlineSmall"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
+        <item name="android:textSize">24sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight" tools:targetApi="28">32sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.TitleLarge"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
+        <item name="android:textSize">22sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight" tools:targetApi="28">28sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.TitleMedium"
+        parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textSize">16sp</item>
+        <item name="android:letterSpacing">0.009375</item>
+        <item name="android:lineHeight" tools:targetApi="28">24sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.TitleSmall"
+        parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textSize">14sp</item>
+        <item name="android:letterSpacing">0.00714286</item>
+        <item name="android:lineHeight" tools:targetApi="28">20sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.LabelLarge"
+        parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textSize">14sp</item>
+        <item name="android:letterSpacing">0.00714286</item>
+        <item name="android:lineHeight" tools:targetApi="28">20sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.LabelMedium"
+        parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textSize">12sp</item>
+        <item name="android:letterSpacing">0.04166667</item>
+        <item name="android:lineHeight" tools:targetApi="28">16sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.LabelSmall"
+        parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textSize">11sp</item>
+        <item name="android:letterSpacing">0.04545455</item>
+        <item name="android:lineHeight" tools:targetApi="28">16sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.BodyLarge"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textSize">16sp</item>
+        <item name="android:letterSpacing">0.03125</item>
+        <item name="android:lineHeight" tools:targetApi="28">24sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.BodyMedium"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textSize">14sp</item>
+        <item name="android:letterSpacing">0.01785714</item>
+        <item name="android:lineHeight" tools:targetApi="28">20sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.BodySmall"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textSize">12sp</item>
+        <item name="android:letterSpacing">0.03333333</item>
+        <item name="android:lineHeight" tools:targetApi="28">16sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.DisplayLarge.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:textSize">57sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight" tools:targetApi="28">64sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.DisplayMedium.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:textSize">45sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight" tools:targetApi="28">52sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.DisplaySmall.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:textSize">36sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight" tools:targetApi="28">44sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.HeadlineLarge.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:textSize">32sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight" tools:targetApi="28">40sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.HeadlineMedium.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:textSize">28sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight" tools:targetApi="28">36sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.HeadlineSmall.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:textSize">24sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight" tools:targetApi="28">32sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.TitleLarge.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+        <item name="android:textSize">22sp</item>
+        <item name="android:letterSpacing">0</item>
+        <item name="android:lineHeight" tools:targetApi="28">28sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.TitleMedium.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textStyle">bold</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:letterSpacing">0.009375</item>
+        <item name="android:lineHeight" tools:targetApi="28">24sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.TitleSmall.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textStyle">bold</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:letterSpacing">0.00714286</item>
+        <item name="android:lineHeight" tools:targetApi="28">20sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.LabelLarge.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textStyle">bold</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:letterSpacing">0.00714286</item>
+        <item name="android:lineHeight" tools:targetApi="28">20sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.LabelMedium.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textStyle">bold</item>
+        <item name="android:textSize">12sp</item>
+        <item name="android:letterSpacing">0.04166667</item>
+        <item name="android:lineHeight" tools:targetApi="28">16sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.LabelSmall.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault">
+        <item name="android:textStyle">bold</item>
+        <item name="android:textSize">11sp</item>
+        <item name="android:letterSpacing">0.04545455</item>
+        <item name="android:lineHeight" tools:targetApi="28">16sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+
+    <style name="TextAppearance.SettingsLib.BodyLarge.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:letterSpacing">0.009375</item>
+        <item name="android:lineHeight" tools:targetApi="28">24sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.BodyMedium.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">14sp</item>
+        <item name="android:letterSpacing">0.01785714</item>
+        <item name="android:lineHeight" tools:targetApi="28">20sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+    <style name="TextAppearance.SettingsLib.BodySmall.Emphasized"
+        parent="@android:style/TextAppearance.DeviceDefault.Medium">
+        <item name="android:textStyle">normal</item>
+        <item name="android:textSize">12sp</item>
+        <item name="android:letterSpacing">0.03333333</item>
+        <item name="android:lineHeight" tools:targetApi="28">16sp</item>
+        <item name="android:textAllCaps">false</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/StatusBannerPreference/res/layout/settingslib_expressive_preference_statusbanner.xml b/packages/SettingsLib/StatusBannerPreference/res/layout/settingslib_expressive_preference_statusbanner.xml
index 9a3e5b9..083b862 100644
--- a/packages/SettingsLib/StatusBannerPreference/res/layout/settingslib_expressive_preference_statusbanner.xml
+++ b/packages/SettingsLib/StatusBannerPreference/res/layout/settingslib_expressive_preference_statusbanner.xml
@@ -72,7 +72,7 @@
                     android:layout_height="wrap_content"
                     android:hyphenationFrequency="normalFast"
                     android:lineBreakWordStyle="phrase"
-                    android:textAppearance="@style/SettingsLibTextAppearance.Emphasized.Title.Large"/>
+                    android:textAppearance="@style/TextAppearance.SettingsLib.TitleLarge.Emphasized"/>
 
                 <TextView
                     android:id="@android:id/summary"
@@ -81,7 +81,7 @@
                     android:hyphenationFrequency="normalFast"
                     android:lineBreakWordStyle="phrase"
                     android:maxLines="3"
-                    android:textAppearance="@style/SettingsLibTextAppearance.Primary.Body.Medium"/>
+                    android:textAppearance="@style/TextAppearance.SettingsLib.BodyMedium"/>
             </LinearLayout>
         </LinearLayout>
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeController.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeController.java
new file mode 100644
index 0000000..7f0c126
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeController.java
@@ -0,0 +1,415 @@
+/*
+ * 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.bluetooth;
+
+import static android.bluetooth.AudioInputControl.MUTE_DISABLED;
+import static android.bluetooth.AudioInputControl.MUTE_MUTED;
+import static android.bluetooth.AudioInputControl.MUTE_NOT_MUTED;
+
+import static com.android.settingslib.bluetooth.HearingDeviceLocalDataManager.Data.INVALID_VOLUME;
+
+import android.bluetooth.AudioInputControl;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * AmbientVolumeController manages the {@link AudioInputControl}s of
+ * {@link AudioInputControl#AUDIO_INPUT_TYPE_AMBIENT} on the remote device.
+ */
+public class AmbientVolumeController implements LocalBluetoothProfileManager.ServiceListener {
+
+    private static final boolean DEBUG = true;
+    private static final String TAG = "AmbientController";
+
+    private final LocalBluetoothProfileManager mProfileManager;
+    private final VolumeControlProfile mVolumeControlProfile;
+    private final Map<BluetoothDevice, List<AudioInputControl>> mDeviceAmbientControlsMap =
+            new ArrayMap<>();
+    private final Map<BluetoothDevice, AmbientCallback> mDeviceCallbackMap = new ArrayMap<>();
+    final Map<BluetoothDevice, RemoteAmbientState> mDeviceAmbientStateMap =
+            new ArrayMap<>();
+    @Nullable
+    private final AmbientVolumeControlCallback mCallback;
+
+    public AmbientVolumeController(
+            @NonNull LocalBluetoothProfileManager profileManager,
+            @Nullable AmbientVolumeControlCallback callback) {
+        mProfileManager = profileManager;
+        mVolumeControlProfile = profileManager.getVolumeControlProfile();
+        if (mVolumeControlProfile != null && !mVolumeControlProfile.isProfileReady()) {
+            mProfileManager.addServiceListener(this);
+        }
+        mCallback = callback;
+    }
+
+    @Override
+    public void onServiceConnected() {
+        if (mVolumeControlProfile != null && mVolumeControlProfile.isProfileReady()) {
+            mProfileManager.removeServiceListener(this);
+            if (mCallback != null) {
+                mCallback.onVolumeControlServiceConnected();
+            }
+        }
+    }
+
+    @Override
+    public void onServiceDisconnected() {
+        // Do nothing
+    }
+
+    /**
+     * Registers the same {@link AmbientCallback} on all ambient control points of the remote
+     * device. The {@link AmbientCallback} will pass the event to registered
+     * {@link AmbientVolumeControlCallback} if exists.
+     *
+     * @param executor the executor to run the callback
+     * @param device the remote device
+     */
+    public void registerCallback(@NonNull Executor executor, @NonNull BluetoothDevice device) {
+        AmbientCallback ambientCallback = new AmbientCallback(device, mCallback);
+        synchronized (mDeviceCallbackMap) {
+            mDeviceCallbackMap.put(device, ambientCallback);
+        }
+
+        // register callback on all ambient input control points of this device
+        List<AudioInputControl> controls = getAmbientControls(device);
+        controls.forEach((control) -> {
+            try {
+                control.registerCallback(executor, ambientCallback);
+            } catch (IllegalArgumentException e) {
+                // The callback was already registered
+                Log.i(TAG, "Skip registering the callback, " + e.getMessage());
+            }
+        });
+    }
+
+    /**
+     * Unregisters the {@link AmbientCallback} on all ambient control points of the remote
+     * device which is previously registered with {@link #registerCallback}.
+     *
+     * @param device the remote device
+     */
+    public void unregisterCallback(@NonNull BluetoothDevice device) {
+        AmbientCallback ambientCallback;
+        synchronized (mDeviceCallbackMap) {
+            ambientCallback = mDeviceCallbackMap.remove(device);
+        }
+        if (ambientCallback == null) {
+            // callback not found, no need to unregister
+            return;
+        }
+
+        // unregister callback on all ambient input control points of this device
+        List<AudioInputControl> controls = getAmbientControls(device);
+        controls.forEach(control -> {
+            try {
+                control.unregisterCallback(ambientCallback);
+            } catch (IllegalArgumentException e) {
+                // The callback was never registered or was already unregistered
+                Log.i(TAG, "Skip unregistering the callback, " + e.getMessage());
+            }
+        });
+    }
+
+    /**
+     * Gets the gain setting max value from first ambient control point of the remote device.
+     *
+     * @param device the remote device
+     */
+    public int getAmbientMax(@NonNull BluetoothDevice device) {
+        List<AudioInputControl> ambientControls = getAmbientControls(device);
+        int value = INVALID_VOLUME;
+        if (!ambientControls.isEmpty()) {
+            value = ambientControls.getFirst().getGainSettingMax();
+        }
+        return value;
+    }
+
+    /**
+     * Gets the gain setting min value from first ambient control point of the remote device.
+     *
+     * @param device the remote device
+     */
+    public int getAmbientMin(@NonNull BluetoothDevice device) {
+        List<AudioInputControl> ambientControls = getAmbientControls(device);
+        int value = INVALID_VOLUME;
+        if (!ambientControls.isEmpty()) {
+            value = ambientControls.getFirst().getGainSettingMin();
+        }
+        return value;
+    }
+
+    /**
+     * Gets the latest values in {@link RemoteAmbientState}.
+     *
+     * @param device the remote device
+     * @return the {@link RemoteAmbientState} represents current remote ambient control point state
+     */
+    @Nullable
+    public RemoteAmbientState refreshAmbientState(@Nullable BluetoothDevice device) {
+        if (device == null || !device.isConnected()) {
+            return null;
+        }
+        int gainSetting = getAmbient(device);
+        int mute = getMute(device);
+        return new RemoteAmbientState(gainSetting, mute);
+    }
+
+    /**
+     * Gets the gain setting value from first ambient control point of the remote device and
+     * stores it in cached {@link RemoteAmbientState}.
+     *
+     * When any audio input point receives {@link AmbientCallback#onGainSettingChanged(int)}
+     * callback, only the changed value which is different from the value stored in the cached
+     * state will be notified to the {@link AmbientVolumeControlCallback} of this controller.
+     *
+     * @param device the remote device
+     */
+    public int getAmbient(@NonNull BluetoothDevice device) {
+        List<AudioInputControl> ambientControls = getAmbientControls(device);
+        int value = INVALID_VOLUME;
+        if (!ambientControls.isEmpty()) {
+            synchronized (mDeviceAmbientStateMap) {
+                value = ambientControls.getFirst().getGainSetting();
+                RemoteAmbientState state = mDeviceAmbientStateMap.getOrDefault(device,
+                        new RemoteAmbientState(INVALID_VOLUME, MUTE_DISABLED));
+                RemoteAmbientState updatedState = new RemoteAmbientState(value, state.mute);
+                mDeviceAmbientStateMap.put(device, updatedState);
+            }
+        }
+        return value;
+    }
+
+    /**
+     * Sets the gain setting value to all ambient control points of the remote device.
+     *
+     * @param device the remote device
+     * @param value the gain setting value to be updated
+     */
+    public void setAmbient(@NonNull BluetoothDevice device, int value) {
+        if (DEBUG) {
+            Log.d(TAG, "setAmbient, value:" + value + ", device:" + device);
+        }
+        List<AudioInputControl> ambientControls = getAmbientControls(device);
+        ambientControls.forEach(control -> control.setGainSetting(value));
+    }
+
+    /**
+     * Gets the mute state from first ambient control point of the remote device and
+     * stores it in cached {@link RemoteAmbientState}. The value will be one of
+     * {@link AudioInputControl.Mute}.
+     *
+     * When any audio input point receives {@link AmbientCallback#onMuteChanged(int)} callback,
+     * only the changed value which is different from the value stored in the cached state will
+     * be notified to the {@link AmbientVolumeControlCallback} of this controller.
+     *
+     * @param device the remote device
+     */
+    public int getMute(@NonNull BluetoothDevice device) {
+        List<AudioInputControl> ambientControls = getAmbientControls(device);
+        int value = MUTE_DISABLED;
+        if (!ambientControls.isEmpty()) {
+            synchronized (mDeviceAmbientStateMap) {
+                value = ambientControls.getFirst().getMute();
+                RemoteAmbientState state = mDeviceAmbientStateMap.getOrDefault(device,
+                        new RemoteAmbientState(INVALID_VOLUME, MUTE_DISABLED));
+                RemoteAmbientState updatedState = new RemoteAmbientState(state.gainSetting, value);
+                mDeviceAmbientStateMap.put(device, updatedState);
+            }
+        }
+        return value;
+    }
+
+    /**
+     * Sets the mute state to all ambient control points of the remote device.
+     *
+     * @param device the remote device
+     * @param muted the mute state to be updated
+     */
+    public void setMuted(@NonNull BluetoothDevice device, boolean muted) {
+        if (DEBUG) {
+            Log.d(TAG, "setMuted, muted:" + muted + ", device:" + device);
+        }
+        List<AudioInputControl> ambientControls = getAmbientControls(device);
+        ambientControls.forEach(control -> {
+            try {
+                control.setMute(muted ? MUTE_MUTED : MUTE_NOT_MUTED);
+            } catch (IllegalStateException e) {
+                // Sometimes remote will throw this exception due to initialization not done
+                // yet. Catch it to prevent crashes on UI.
+                Log.w(TAG, "Remote mute state is currently disabled.");
+            }
+        });
+    }
+
+    /**
+     * Checks if there's any valid ambient control point exists on the remote device
+     *
+     * @param device the remote device
+     */
+    public boolean isAmbientControlAvailable(@NonNull BluetoothDevice device) {
+        final boolean hasAmbientControlPoint = !getAmbientControls(device).isEmpty();
+        final boolean connectedToVcp = mVolumeControlProfile.getConnectionStatus(device)
+                == BluetoothProfile.STATE_CONNECTED;
+        return hasAmbientControlPoint && connectedToVcp;
+    }
+
+    @NonNull
+    private List<AudioInputControl> getAmbientControls(@NonNull BluetoothDevice device) {
+        if (mVolumeControlProfile == null) {
+            return Collections.emptyList();
+        }
+        synchronized (mDeviceAmbientControlsMap) {
+            if (mDeviceAmbientControlsMap.containsKey(device)) {
+                return mDeviceAmbientControlsMap.get(device);
+            }
+            List<AudioInputControl> ambientControls =
+                    mVolumeControlProfile.getAudioInputControlServices(device).stream().filter(
+                            this::isValidAmbientControl).toList();
+            if (!ambientControls.isEmpty()) {
+                mDeviceAmbientControlsMap.put(device, ambientControls);
+            }
+            return ambientControls;
+        }
+    }
+
+    private boolean isValidAmbientControl(AudioInputControl control) {
+        boolean isAmbientControl =
+                control.getAudioInputType() == AudioInputControl.AUDIO_INPUT_TYPE_AMBIENT;
+        boolean isManual = control.getGainMode() == AudioInputControl.GAIN_MODE_MANUAL
+                || control.getGainMode() == AudioInputControl.GAIN_MODE_MANUAL_ONLY;
+        boolean isActive =
+                control.getAudioInputStatus() == AudioInputControl.AUDIO_INPUT_STATUS_ACTIVE;
+
+        return isAmbientControl && isManual && isActive;
+    }
+
+    /**
+     * Callback providing information about the status and received events of
+     * {@link AmbientVolumeController}.
+     */
+    public interface AmbientVolumeControlCallback {
+
+        /** This method is called when the Volume Control Service is connected */
+        default void onVolumeControlServiceConnected() {
+        }
+
+        /**
+         * This method is called when one of the remote device's ambient control point's gain
+         * settings value is changed.
+         *
+         * @param device the remote device
+         * @param gainSettings the new gain setting value
+         */
+        default void onAmbientChanged(@NonNull BluetoothDevice device, int gainSettings) {
+        }
+
+        /**
+         * This method is called when one of the remote device's ambient control point's mute
+         * state is changed.
+         *
+         * @param device the remote device
+         * @param mute the new mute state
+         */
+        default void onMuteChanged(@NonNull BluetoothDevice device, int mute) {
+        }
+
+        /**
+         * This method is called when any command to the remote device's ambient control point
+         * is failed.
+         *
+         * @param device the remote device.
+         */
+        default void onCommandFailed(@NonNull BluetoothDevice device) {
+        }
+    }
+
+    /**
+     * A wrapper callback that will pass {@link AudioInputControl.AudioInputCallback} with extra
+     * device information to {@link AmbientVolumeControlCallback}.
+     */
+    class AmbientCallback implements AudioInputControl.AudioInputCallback {
+
+        private final BluetoothDevice mDevice;
+        private final AmbientVolumeControlCallback mCallback;
+
+        AmbientCallback(@NonNull BluetoothDevice device,
+                @Nullable AmbientVolumeControlCallback callback) {
+            mDevice = device;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onGainSettingChanged(int gainSetting) {
+            if (mCallback != null) {
+                synchronized (mDeviceAmbientStateMap) {
+                    RemoteAmbientState previousState = mDeviceAmbientStateMap.get(mDevice);
+                    if (previousState.gainSetting != gainSetting) {
+                        mCallback.onAmbientChanged(mDevice, gainSetting);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onSetGainSettingFailed() {
+            Log.w(TAG, "onSetGainSettingFailed, device=" + mDevice);
+            if (mCallback != null) {
+                mCallback.onCommandFailed(mDevice);
+            }
+        }
+
+        @Override
+        public void onMuteChanged(int mute) {
+            if (mCallback != null) {
+                synchronized (mDeviceAmbientStateMap) {
+                    RemoteAmbientState previousState = mDeviceAmbientStateMap.get(mDevice);
+                    if (previousState.mute != mute) {
+                        mCallback.onMuteChanged(mDevice, mute);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onSetMuteFailed() {
+            Log.w(TAG, "onSetMuteFailed, device=" + mDevice);
+            if (mCallback != null) {
+                mCallback.onCommandFailed(mDevice);
+            }
+        }
+    }
+
+    public record RemoteAmbientState(int gainSetting, int mute) {
+        public boolean isMutable() {
+            return mute != MUTE_DISABLED;
+        }
+        public boolean isMuted() {
+            return mute == MUTE_MUTED;
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index b58983f..257c935 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1591,6 +1591,12 @@
 
     private int getHearingDeviceSummaryRes(int leftBattery, int rightBattery,
             boolean shortSummary) {
+        if (getDeviceSide() == HearingAidInfo.DeviceSide.SIDE_MONO
+                || getDeviceSide() == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) {
+            return !shortSummary && (getBatteryLevel() > BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+                    ? R.string.bluetooth_active_battery_level
+                    : R.string.bluetooth_active_no_battery_level;
+        }
         boolean isLeftDeviceConnected = getConnectedHearingAidSide(
                 HearingAidInfo.DeviceSide.SIDE_LEFT).isPresent();
         boolean isRightDeviceConnected = getConnectedHearingAidSide(
@@ -1646,8 +1652,7 @@
             @HearingAidInfo.DeviceSide int side) {
         return Stream.concat(Stream.of(this, mSubDevice), mMemberDevices.stream())
                 .filter(Objects::nonNull)
-                .filter(device -> device.getDeviceSide() == side
-                        || device.getDeviceSide() == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT)
+                .filter(device -> device.getDeviceSide() == side)
                 .filter(device -> device.getDevice().isConnected())
                 // For hearing aids, we should expect only one device assign to one side, but if
                 // it happens, we don't care which one.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 7fdb32c..b754706 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -153,7 +153,7 @@
     /**
      * Returns device summary of the pair of the hearing aid / CSIP passed as the parameter.
      *
-     * @param CachedBluetoothDevice device
+     * @param device the remote device
      * @return Device summary, or if the pair does not exist or if it is not a hearing aid or
      * a CSIP set member, then {@code null}.
      */
@@ -394,6 +394,7 @@
     }
 
     public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) {
+        mHearingAidDeviceManager.clearLocalDataIfNeeded(device);
         device.setGroupId(BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
         CachedBluetoothDevice mainDevice = mCsipDeviceManager.findMainDevice(device);
         // Should iterate through the cloned set to avoid ConcurrentModificationException
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index fa28cf6..1ca4c2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -18,7 +18,6 @@
 import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothHapClient;
 import android.bluetooth.BluetoothHearingAid;
-import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.le.ScanFilter;
@@ -308,6 +307,10 @@
         }
     }
 
+    void clearLocalDataIfNeeded(CachedBluetoothDevice device) {
+        HearingDeviceLocalDataManager.clear(mContext, device.getDevice());
+    }
+
     private void setAudioRoutingConfig(CachedBluetoothDevice device) {
         AudioDeviceAttributes hearingDeviceAttributes =
                 mRoutingHelper.getMatchedHearingDeviceAttributes(device);
@@ -428,8 +431,7 @@
                 p -> p instanceof HapClientProfile)) {
             int audioLocation = leAudioProfile.getAudioLocation(cachedDevice.getDevice());
             int hearingAidType = hapClientProfile.getHearingAidType(cachedDevice.getDevice());
-            if (audioLocation != BluetoothLeAudio.AUDIO_LOCATION_INVALID
-                    && hearingAidType != HapClientProfile.HearingAidType.TYPE_INVALID) {
+            if (hearingAidType != HapClientProfile.HearingAidType.TYPE_INVALID) {
                 final HearingAidInfo info = new HearingAidInfo.Builder()
                         .setLeAudioLocation(audioLocation)
                         .setHapDeviceType(hearingAidType)
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidInfo.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidInfo.java
index ef08c92..8399824 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidInfo.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidInfo.java
@@ -34,6 +34,7 @@
             DeviceSide.SIDE_LEFT,
             DeviceSide.SIDE_RIGHT,
             DeviceSide.SIDE_LEFT_AND_RIGHT,
+            DeviceSide.SIDE_MONO
     })
 
     /** Side definition for hearing aids. */
@@ -42,6 +43,7 @@
         int SIDE_LEFT = 0;
         int SIDE_RIGHT = 1;
         int SIDE_LEFT_AND_RIGHT = 2;
+        int SIDE_MONO = 3;
     }
 
     @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
@@ -124,6 +126,9 @@
 
     @DeviceSide
     private static int convertLeAudioLocationToInternalSide(int leAudioLocation) {
+        if (leAudioLocation == BluetoothLeAudio.AUDIO_LOCATION_MONO) {
+            return DeviceSide.SIDE_MONO;
+        }
         boolean isLeft = (leAudioLocation & LE_AUDIO_LOCATION_LEFT) != 0;
         boolean isRight = (leAudioLocation & LE_AUDIO_LOCATION_RIGHT) != 0;
         if (isLeft && isRight) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java
index 7a64965..6725558 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManager.java
@@ -86,6 +86,17 @@
         mSettingsObserver = new SettingsObserver(ThreadUtils.getUiThreadHandler());
     }
 
+    /**
+     * Clears the local data of the device. This method should be called when the device is
+     * unpaired.
+     */
+    public static void clear(@NonNull Context context, @NonNull BluetoothDevice device) {
+        HearingDeviceLocalDataManager manager = new HearingDeviceLocalDataManager(context);
+        manager.getLocalDataFromSettings();
+        manager.remove(device);
+        manager.putAmbientVolumeSettings();
+    }
+
     /** Starts the manager. Loads the data from Settings and start observing any changes. */
     public synchronized void start() {
         if (mIsStarted) {
@@ -141,6 +152,7 @@
      * Puts the local data of the corresponding hearing device.
      *
      * @param device the device to update the local data
+     * @param data the local data to be stored
      */
     private void put(BluetoothDevice device, Data data) {
         if (device == null) {
@@ -148,7 +160,11 @@
         }
         synchronized (sLock) {
             final String addr = device.getAnonymizedAddress();
-            mAddrToDataMap.put(addr, data);
+            if (data == null) {
+                mAddrToDataMap.remove(addr);
+            } else {
+                mAddrToDataMap.put(addr, data);
+            }
             if (mListener != null && mListenerExecutor != null) {
                 mListenerExecutor.execute(() -> mListener.onDeviceLocalDataChange(addr, data));
             }
@@ -156,6 +172,24 @@
     }
 
     /**
+     * Removes the local data of the corresponding hearing device.
+     *
+     * @param device the device to remove the local data
+     */
+    private void remove(BluetoothDevice device) {
+        if (device == null) {
+            return;
+        }
+        synchronized (sLock) {
+            final String addr = device.getAnonymizedAddress();
+            mAddrToDataMap.remove(addr);
+            if (mListener != null && mListenerExecutor != null) {
+                mListenerExecutor.execute(() -> mListener.onDeviceLocalDataChange(addr, null));
+            }
+        }
+    }
+
+    /**
      * Updates the ambient volume of the corresponding hearing device. This should be called after
      * {@link #start()} is called().
      *
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
index a03977c1..785bcbf 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
@@ -21,15 +21,26 @@
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
+import static android.security.advancedprotection.AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY;
+
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.admin.Authority;
+import android.app.admin.DeviceAdminAuthority;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.DpcAuthority;
+import android.app.admin.EnforcingAdmin;
+import android.app.admin.RoleAuthority;
+import android.app.admin.UnknownAuthority;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -37,8 +48,13 @@
 import android.content.pm.UserInfo;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Answers;
@@ -52,6 +68,8 @@
 
 @RunWith(RobolectricTestRunner.class)
 public class RestrictedLockUtilsTest {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Mock
     private Context mContext;
@@ -66,6 +84,7 @@
 
     private final int mUserId = 194;
     private final int mProfileId = 160;
+    private final String mPackage = "test.pkg";
     private final ComponentName mAdmin1 = new ComponentName("admin1", "admin1class");
     private final ComponentName mAdmin2 = new ComponentName("admin2", "admin2class");
 
@@ -85,6 +104,7 @@
         RestrictedLockUtilsInternal.sProxy = mProxy;
     }
 
+    @RequiresFlagsDisabled(android.security.Flags.FLAG_AAPM_API)
     @Test
     public void checkIfRestrictionEnforced_deviceOwner()
             throws PackageManager.NameNotFoundException {
@@ -109,6 +129,7 @@
         assertThat(enforcedAdmin.component).isEqualTo(mAdmin1);
     }
 
+    @RequiresFlagsDisabled(android.security.Flags.FLAG_AAPM_API)
     @Test
     public void checkIfRestrictionEnforced_profileOwner()
             throws PackageManager.NameNotFoundException {
@@ -133,6 +154,125 @@
         assertThat(enforcedAdmin.component).isEqualTo(mAdmin1);
     }
 
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_API)
+    @Test
+    public void checkIfRestrictionEnforced_getEnforcingAdminExists() {
+        UserManager.EnforcingUser enforcingUser = new UserManager.EnforcingUser(mUserId,
+                UserManager.RESTRICTION_SOURCE_PROFILE_OWNER);
+        final String userRestriction = UserManager.DISALLOW_UNINSTALL_APPS;
+        final EnforcingAdmin enforcingAdmin = new EnforcingAdmin(mPackage,
+                UnknownAuthority.UNKNOWN_AUTHORITY, UserHandle.of(mUserId), mAdmin1);
+
+        when(mUserManager.getUserRestrictionSources(userRestriction,
+                UserHandle.of(mUserId)))
+                .thenReturn(Collections.singletonList(enforcingUser));
+        when(mDevicePolicyManager.getEnforcingAdmin(mUserId, userRestriction))
+                .thenReturn(enforcingAdmin);
+
+        EnforcedAdmin enforcedAdmin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
+                mContext, userRestriction, mUserId);
+
+        assertThat(enforcedAdmin).isNotNull();
+        assertThat(enforcedAdmin.enforcedRestriction).isEqualTo(userRestriction);
+        assertThat(enforcedAdmin.component).isEqualTo(enforcingAdmin.getComponentName());
+        assertThat(enforcedAdmin.user).isEqualTo(enforcingAdmin.getUserHandle());
+    }
+
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_API)
+    @Test
+    public void checkIfRestrictionEnforced_getEnforcingAdminReturnsNull_deviceOwner()
+            throws PackageManager.NameNotFoundException {
+        UserManager.EnforcingUser enforcingUser = new UserManager.EnforcingUser(mUserId,
+                UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
+        final String userRestriction = UserManager.DISALLOW_UNINSTALL_APPS;
+
+        when(mUserManager.getUserRestrictionSources(userRestriction,
+                UserHandle.of(mUserId)))
+                .thenReturn(Collections.singletonList(enforcingUser));
+        when(mDevicePolicyManager.getEnforcingAdmin(mUserId, userRestriction))
+                .thenReturn(null);
+        when(mContext.createPackageContextAsUser(any(), eq(0),
+                eq(UserHandle.of(mUserId))))
+                .thenReturn(mContext);
+
+        setUpDeviceOwner(mAdmin1, mUserId);
+
+        EnforcedAdmin enforcedAdmin = RestrictedLockUtilsInternal
+                .checkIfRestrictionEnforced(mContext, userRestriction, mUserId);
+
+        assertThat(enforcedAdmin).isNotNull();
+        assertThat(enforcedAdmin.enforcedRestriction).isEqualTo(userRestriction);
+        assertThat(enforcedAdmin.component).isEqualTo(mAdmin1);
+    }
+
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_API)
+    @Test
+    public void checkIfRestrictionEnforced_getEnforcingAdminReturnsNull_profileOwner()
+            throws PackageManager.NameNotFoundException {
+        UserManager.EnforcingUser enforcingUser = new UserManager.EnforcingUser(mUserId,
+                UserManager.RESTRICTION_SOURCE_PROFILE_OWNER);
+        final String userRestriction = UserManager.DISALLOW_UNINSTALL_APPS;
+
+        when(mUserManager.getUserRestrictionSources(userRestriction,
+                UserHandle.of(mUserId)))
+                .thenReturn(Collections.singletonList(enforcingUser));
+        when(mDevicePolicyManager.getEnforcingAdmin(mUserId, userRestriction))
+                .thenReturn(null);
+        when(mContext.createPackageContextAsUser(any(), eq(0),
+                eq(UserHandle.of(mUserId))))
+                .thenReturn(mContext);
+
+        setUpProfileOwner(mAdmin1);
+
+        EnforcedAdmin enforcedAdmin = RestrictedLockUtilsInternal
+                .checkIfRestrictionEnforced(mContext, userRestriction, mUserId);
+
+        assertThat(enforcedAdmin).isNotNull();
+        assertThat(enforcedAdmin.enforcedRestriction).isEqualTo(userRestriction);
+        assertThat(enforcedAdmin.component).isEqualTo(mAdmin1);
+    }
+
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_API)
+    @Test
+    public void isPolicyEnforcedByAdvancedProtection_notEnforced_returnsFalse() {
+        final String userRestriction = UserManager.DISALLOW_UNINSTALL_APPS;
+        final Authority[] allNonAdvancedProtectionAuthorities = new Authority[] {
+                UnknownAuthority.UNKNOWN_AUTHORITY,
+                DeviceAdminAuthority.DEVICE_ADMIN_AUTHORITY,
+                DpcAuthority.DPC_AUTHORITY,
+                new RoleAuthority(Collections.singleton("some-role"))
+        };
+
+        for (Authority authority : allNonAdvancedProtectionAuthorities) {
+            final EnforcingAdmin enforcingAdmin = new EnforcingAdmin(mPackage, authority,
+                    UserHandle.of(mUserId), mAdmin1);
+
+            when(mDevicePolicyManager.getEnforcingAdmin(mUserId, userRestriction))
+                    .thenReturn(enforcingAdmin);
+
+            assertWithMessage(authority + " is not an advanced protection authority")
+                    .that(RestrictedLockUtilsInternal.isPolicyEnforcedByAdvancedProtection(
+                            mContext, userRestriction, mUserId))
+                    .isFalse();
+        }
+    }
+
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_API)
+    @Test
+    public void isPolicyEnforcedByAdvancedProtection_enforced_returnsTrue() {
+        final Authority advancedProtectionAuthority = new UnknownAuthority(
+                ADVANCED_PROTECTION_SYSTEM_ENTITY);
+        final EnforcingAdmin advancedProtectionEnforcingAdmin = new EnforcingAdmin(mPackage,
+                advancedProtectionAuthority, UserHandle.of(mUserId), mAdmin1);
+        final String userRestriction = UserManager.DISALLOW_UNINSTALL_APPS;
+
+        when(mDevicePolicyManager.getEnforcingAdmin(mUserId, userRestriction))
+                .thenReturn(advancedProtectionEnforcingAdmin);
+
+        assertThat(RestrictedLockUtilsInternal.isPolicyEnforcedByAdvancedProtection(mContext,
+                userRestriction, mUserId)).isTrue();
+    }
+
     @Test
     public void checkIfDevicePolicyServiceDisabled_noEnforceAdminForManagedProfile() {
         when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(null);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
index 7ad54e1..dbbbd5b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
@@ -16,7 +16,10 @@
 
 package com.android.settingslib;
 
+import static android.security.advancedprotection.AdvancedProtectionManager.ADVANCED_PROTECTION_SYSTEM_ENTITY;
+
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
@@ -26,10 +29,23 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.admin.Authority;
+import android.app.admin.DeviceAdminAuthority;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyResourcesManager;
+import android.app.admin.DpcAuthority;
+import android.app.admin.EnforcingAdmin;
+import android.app.admin.RoleAuthority;
+import android.app.admin.UnknownAuthority;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.view.View;
 import android.widget.TextView;
 
@@ -37,14 +53,19 @@
 import androidx.preference.PreferenceViewHolder;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 
+import java.util.Collections;
+
 @RunWith(RobolectricTestRunner.class)
 public class RestrictedPreferenceHelperTest {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Mock
     private Context mContext;
@@ -57,6 +78,11 @@
     @Mock
     private RestrictedTopLevelPreference mRestrictedTopLevelPreference;
 
+    private final String mPackage = "test.pkg";
+    private final ComponentName mAdmin = new ComponentName("admin", "adminclass");
+    private final Authority mAdvancedProtectionAuthority = new UnknownAuthority(
+            ADVANCED_PROTECTION_SYSTEM_ENTITY);
+
     private PreferenceViewHolder mViewHolder;
     private RestrictedPreferenceHelper mHelper;
 
@@ -71,6 +97,7 @@
         mHelper = new RestrictedPreferenceHelper(mContext, mPreference, null);
     }
 
+    @RequiresFlagsDisabled(android.security.Flags.FLAG_AAPM_API)
     @Test
     public void bindPreference_disabled_shouldDisplayDisabledSummary() {
         final TextView summaryView = mock(TextView.class, RETURNS_DEEP_STUBS);
@@ -101,6 +128,57 @@
         verify(summaryView, never()).setVisibility(View.GONE);
     }
 
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_API)
+    @Test
+    public void bindPreference_disabled_byAdvancedProtection_shouldDisplayDisabledSummary() {
+        final TextView summaryView = mock(TextView.class, RETURNS_DEEP_STUBS);
+        final String userRestriction = UserManager.DISALLOW_UNINSTALL_APPS;
+        final RestrictedLockUtils.EnforcedAdmin enforcedAdmin = new RestrictedLockUtils
+                .EnforcedAdmin(/* component */ null, userRestriction, UserHandle.of(
+                        UserHandle.myUserId()));
+        final EnforcingAdmin advancedProtectionEnforcingAdmin = new EnforcingAdmin(mPackage,
+                mAdvancedProtectionAuthority, UserHandle.of(UserHandle.myUserId()), mAdmin);
+
+        when(mViewHolder.itemView.findViewById(android.R.id.summary))
+                .thenReturn(summaryView);
+        when(mDevicePolicyManager.getEnforcingAdmin(UserHandle.myUserId(), userRestriction))
+                .thenReturn(advancedProtectionEnforcingAdmin);
+        when(mContext.getString(
+                com.android.settingslib.widget.restricted.R.string.disabled_by_advanced_protection))
+                .thenReturn("advanced_protection");
+
+        mHelper.useAdminDisabledSummary(true);
+        mHelper.setDisabledByAdmin(enforcedAdmin);
+        mHelper.onBindViewHolder(mViewHolder);
+
+        verify(summaryView).setText("advanced_protection");
+        verify(summaryView, never()).setVisibility(View.GONE);
+    }
+
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_API)
+    @Test
+    public void bindPreference_disabled_byAdmin_shouldDisplayDisabledSummary() {
+        final TextView summaryView = mock(TextView.class, RETURNS_DEEP_STUBS);
+        final EnforcingAdmin nonAdvancedProtectionEnforcingAdmin = new EnforcingAdmin(mPackage,
+                UnknownAuthority.UNKNOWN_AUTHORITY, UserHandle.of(UserHandle.myUserId()), mAdmin);
+        final String userRestriction = UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY;
+
+        when(mViewHolder.itemView.findViewById(android.R.id.summary))
+                .thenReturn(summaryView);
+        when(mDevicePolicyManager.getEnforcingAdmin(UserHandle.myUserId(), userRestriction))
+                .thenReturn(nonAdvancedProtectionEnforcingAdmin);
+        when(mContext.getString(R.string.disabled_by_admin_summary_text))
+                .thenReturn("test");
+        when(mDevicePolicyResourcesManager.getString(any(), any())).thenReturn("test");
+
+        mHelper.useAdminDisabledSummary(true);
+        mHelper.setDisabledByAdmin(new RestrictedLockUtils.EnforcedAdmin());
+        mHelper.onBindViewHolder(mViewHolder);
+
+        verify(summaryView).setText("test");
+        verify(summaryView, never()).setVisibility(View.GONE);
+    }
+
     @Test
     public void bindPreference_notDisabled_shouldNotHideSummary() {
         final TextView summaryView = mock(TextView.class, RETURNS_DEEP_STUBS);
@@ -157,4 +235,74 @@
 
         assertThat(mHelper.isDisabledByAdmin()).isTrue();
     }
+
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_API)
+    @Test
+    public void setDisabledByAdmin_previousAndCurrentAdminsAreTheSame_returnsFalse() {
+        RestrictedLockUtils.EnforcedAdmin enforcedAdmin =
+                new RestrictedLockUtils.EnforcedAdmin(/* component */ null,
+                        /* enforcedRestriction */ "some_restriction", /* userHandle */ null);
+
+        mHelper.setDisabledByAdmin(enforcedAdmin);
+
+        assertThat(mHelper.setDisabledByAdmin(enforcedAdmin)).isFalse();
+    }
+
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_API)
+    @Test
+    public void setDisabledByAdmin_previousAndCurrentAdminsAreDifferent_returnsTrue() {
+        RestrictedLockUtils.EnforcedAdmin enforcedAdmin1 =
+                new RestrictedLockUtils.EnforcedAdmin(/* component */ null,
+                        /* enforcedRestriction */ "some_restriction", /* userHandle */ null);
+        RestrictedLockUtils.EnforcedAdmin enforcedAdmin2 =
+                new RestrictedLockUtils.EnforcedAdmin(new ComponentName("pkg", "cls"),
+                        /* enforcedRestriction */ "some_restriction", /* userHandle */ null);
+
+        mHelper.setDisabledByAdmin(enforcedAdmin1);
+
+        assertThat(mHelper.setDisabledByAdmin(enforcedAdmin2)).isTrue();
+    }
+
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_API)
+    @Test
+    public void isRestrictionEnforcedByAdvancedProtection_notEnforced_returnsFalse() {
+        final Authority[] allNonAdvancedProtectionAuthorities = new Authority[] {
+                UnknownAuthority.UNKNOWN_AUTHORITY,
+                DeviceAdminAuthority.DEVICE_ADMIN_AUTHORITY,
+                DpcAuthority.DPC_AUTHORITY,
+                new RoleAuthority(Collections.singleton("some-role"))
+        };
+        final String userRestriction = UserManager.DISALLOW_UNINSTALL_APPS;
+
+        for (Authority authority : allNonAdvancedProtectionAuthorities) {
+            final EnforcingAdmin enforcingAdmin = new EnforcingAdmin(mPackage, authority,
+                    UserHandle.of(UserHandle.myUserId()), mAdmin);
+
+            when(mDevicePolicyManager.getEnforcingAdmin(UserHandle.myUserId(), userRestriction))
+                    .thenReturn(enforcingAdmin);
+
+            mHelper.setDisabledByAdmin(new RestrictedLockUtils.EnforcedAdmin(/* component */ null,
+                    userRestriction, UserHandle.of(UserHandle.myUserId())));
+
+            assertWithMessage(authority + " is not an advanced protection authority")
+                    .that(mHelper.isRestrictionEnforcedByAdvancedProtection())
+                    .isFalse();
+        }
+    }
+
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_AAPM_API)
+    @Test
+    public void isRestrictionEnforcedByAdvancedProtection_enforced_returnsTrue() {
+        final EnforcingAdmin advancedProtectionEnforcingAdmin = new EnforcingAdmin(mPackage,
+                mAdvancedProtectionAuthority, UserHandle.of(UserHandle.myUserId()), mAdmin);
+        final String userRestriction = UserManager.DISALLOW_UNINSTALL_APPS;
+
+        when(mDevicePolicyManager.getEnforcingAdmin(UserHandle.myUserId(), userRestriction))
+                .thenReturn(advancedProtectionEnforcingAdmin);
+
+        mHelper.setDisabledByAdmin(new RestrictedLockUtils.EnforcedAdmin(/* component */ null,
+                userRestriction, UserHandle.of(UserHandle.myUserId())));
+
+        assertThat(mHelper.isRestrictionEnforcedByAdvancedProtection()).isTrue();
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeControllerTest.java
new file mode 100644
index 0000000..abc1d226
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/AmbientVolumeControllerTest.java
@@ -0,0 +1,318 @@
+/*
+ * 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.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.AudioInputControl;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+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.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/** Tests for {@link AmbientVolumeController}. */
+@RunWith(RobolectricTestRunner.class)
+public class AmbientVolumeControllerTest {
+
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    private static final String TEST_ADDRESS = "00:00:00:00:11";
+
+    @Mock
+    private LocalBluetoothProfileManager mProfileManager;
+    @Mock
+    private VolumeControlProfile mVolumeControlProfile;
+    @Mock
+    private AmbientVolumeController.AmbientVolumeControlCallback mCallback;
+    @Mock
+    private BluetoothDevice mDevice;
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private AmbientVolumeController mVolumeController;
+
+    @Before
+    public void setUp() {
+        when(mProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControlProfile);
+        when(mDevice.getAddress()).thenReturn(TEST_ADDRESS);
+        when(mDevice.isConnected()).thenReturn(true);
+        mVolumeController = new AmbientVolumeController(mProfileManager, mCallback);
+    }
+
+    @Test
+    public void onServiceConnected_notifyCallback() {
+        when(mVolumeControlProfile.isProfileReady()).thenReturn(true);
+
+        mVolumeController.onServiceConnected();
+
+        verify(mCallback).onVolumeControlServiceConnected();
+    }
+
+    @Test
+    public void isAmbientControlAvailable_validControls_assertTrue() {
+        prepareValidAmbientControls();
+
+        assertThat(mVolumeController.isAmbientControlAvailable(mDevice)).isTrue();
+    }
+
+    @Test
+    public void isAmbientControlAvailable_streamingControls_assertFalse() {
+        prepareStreamingControls();
+
+        assertThat(mVolumeController.isAmbientControlAvailable(mDevice)).isFalse();
+    }
+
+    @Test
+    public void isAmbientControlAvailable_automaticAmbientControls_assertFalse() {
+        prepareAutomaticAmbientControls();
+
+        assertThat(mVolumeController.isAmbientControlAvailable(mDevice)).isFalse();
+    }
+
+    @Test
+    public void isAmbientControlAvailable_inactiveAmbientControls_assertFalse() {
+        prepareInactiveAmbientControls();
+
+        assertThat(mVolumeController.isAmbientControlAvailable(mDevice)).isFalse();
+    }
+
+    @Test
+    public void registerCallback_verifyRegisterOnAllControls() {
+        List<AudioInputControl> controls = prepareValidAmbientControls();
+
+        mVolumeController.registerCallback(mContext.getMainExecutor(), mDevice);
+
+        for (AudioInputControl control : controls) {
+            verify(control).registerCallback(any(Executor.class), any());
+        }
+    }
+
+    @Test
+    public void unregisterCallback_verifyUnregisterOnAllControls() {
+        List<AudioInputControl> controls = prepareValidAmbientControls();
+
+        mVolumeController.registerCallback(mContext.getMainExecutor(), mDevice);
+        mVolumeController.unregisterCallback(mDevice);
+
+        for (AudioInputControl control : controls) {
+            verify(control).unregisterCallback(any());
+        }
+    }
+
+    @Test
+    public void getAmbientMax_verifyGetOnFirstControl() {
+        List<AudioInputControl> controls = prepareValidAmbientControls();
+
+        mVolumeController.getAmbientMax(mDevice);
+
+        verify(controls.getFirst()).getGainSettingMax();
+    }
+
+    @Test
+    public void getAmbientMin_verifyGetOnFirstControl() {
+        List<AudioInputControl> controls = prepareValidAmbientControls();
+
+        mVolumeController.getAmbientMin(mDevice);
+
+        verify(controls.getFirst()).getGainSettingMin();
+    }
+
+    @Test
+    public void getAmbient_verifyGetOnFirstControl() {
+        List<AudioInputControl> controls = prepareValidAmbientControls();
+
+        mVolumeController.getAmbient(mDevice);
+
+        verify(controls.getFirst()).getGainSetting();
+    }
+
+    @Test
+    public void setAmbient_verifySetOnAllControls() {
+        List<AudioInputControl> controls = prepareValidAmbientControls();
+
+        mVolumeController.setAmbient(mDevice, 10);
+
+        for (AudioInputControl control : controls) {
+            verify(control).setGainSetting(10);
+        }
+    }
+
+    @Test
+    public void getMute_verifyGetOnFirstControl() {
+        List<AudioInputControl> controls = prepareValidAmbientControls();
+
+        mVolumeController.getMute(mDevice);
+
+        verify(controls.getFirst()).getMute();
+    }
+
+    @Test
+    public void setMuted_true_verifySetOnAllControls() {
+        List<AudioInputControl> controls = prepareValidAmbientControls();
+
+        mVolumeController.setMuted(mDevice, true);
+
+        for (AudioInputControl control : controls) {
+            verify(control).setMute(AudioInputControl.MUTE_MUTED);
+        }
+    }
+
+    @Test
+    public void setMuted_false_verifySetOnAllControls() {
+        List<AudioInputControl> controls = prepareValidAmbientControls();
+
+        mVolumeController.setMuted(mDevice, false);
+
+        for (AudioInputControl control : controls) {
+            verify(control).setMute(AudioInputControl.MUTE_NOT_MUTED);
+        }
+    }
+
+    @Test
+    public void ambientCallback_onGainSettingChanged_verifyCallbackIsCalledWhenStateChange() {
+        AmbientVolumeController.AmbientCallback ambientCallback =
+                mVolumeController.new AmbientCallback(mDevice, mCallback);
+        final int testAmbient = 10;
+        List<AudioInputControl> controls = prepareValidAmbientControls();
+        when(controls.getFirst().getGainSetting()).thenReturn(testAmbient);
+
+        mVolumeController.refreshAmbientState(mDevice);
+        ambientCallback.onGainSettingChanged(testAmbient);
+        verify(mCallback, never()).onAmbientChanged(mDevice, testAmbient);
+
+        final int updatedTestAmbient = 20;
+        ambientCallback.onGainSettingChanged(updatedTestAmbient);
+        verify(mCallback).onAmbientChanged(mDevice, updatedTestAmbient);
+    }
+
+
+    @Test
+    public void ambientCallback_onSetAmbientFailed_verifyCallbackIsCalled() {
+        AmbientVolumeController.AmbientCallback ambientCallback =
+                mVolumeController.new AmbientCallback(mDevice, mCallback);
+
+        ambientCallback.onSetGainSettingFailed();
+
+        verify(mCallback).onCommandFailed(mDevice);
+    }
+
+    @Test
+    public void ambientCallback_onMuteChanged_verifyCallbackIsCalledWhenStateChange() {
+        AmbientVolumeController.AmbientCallback ambientCallback =
+                mVolumeController.new AmbientCallback(mDevice, mCallback);
+        final int testMute = 0;
+        List<AudioInputControl> controls = prepareValidAmbientControls();
+        when(controls.getFirst().getMute()).thenReturn(testMute);
+
+        mVolumeController.refreshAmbientState(mDevice);
+        ambientCallback.onMuteChanged(testMute);
+        verify(mCallback, never()).onMuteChanged(mDevice, testMute);
+
+        final int updatedTestMute = 1;
+        ambientCallback.onMuteChanged(updatedTestMute);
+        verify(mCallback).onMuteChanged(mDevice, updatedTestMute);
+    }
+
+    @Test
+    public void ambientCallback_onSetMuteFailed_verifyCallbackIsCalled() {
+        AmbientVolumeController.AmbientCallback ambientCallback =
+                mVolumeController.new AmbientCallback(mDevice, mCallback);
+
+        ambientCallback.onSetMuteFailed();
+
+        verify(mCallback).onCommandFailed(mDevice);
+    }
+
+    private List<AudioInputControl> prepareValidAmbientControls() {
+        List<AudioInputControl> controls = new ArrayList<>();
+        final int controlsCount = 2;
+        for (int i = 0; i < controlsCount; i++) {
+            controls.add(prepareAudioInputControl(
+                    AudioInputControl.AUDIO_INPUT_TYPE_AMBIENT,
+                    AudioInputControl.GAIN_MODE_MANUAL,
+                    AudioInputControl.AUDIO_INPUT_STATUS_ACTIVE));
+        }
+        when(mVolumeControlProfile.getAudioInputControlServices(mDevice)).thenReturn(controls);
+        return controls;
+    }
+
+    private List<AudioInputControl> prepareStreamingControls() {
+        List<AudioInputControl> controls = new ArrayList<>();
+        final int controlsCount = 2;
+        for (int i = 0; i < controlsCount; i++) {
+            controls.add(prepareAudioInputControl(
+                    AudioInputControl.AUDIO_INPUT_TYPE_STREAMING,
+                    AudioInputControl.GAIN_MODE_MANUAL,
+                    AudioInputControl.AUDIO_INPUT_STATUS_ACTIVE));
+        }
+        when(mVolumeControlProfile.getAudioInputControlServices(mDevice)).thenReturn(controls);
+        return controls;
+    }
+
+    private List<AudioInputControl> prepareAutomaticAmbientControls() {
+        List<AudioInputControl> controls = new ArrayList<>();
+        final int controlsCount = 2;
+        for (int i = 0; i < controlsCount; i++) {
+            controls.add(prepareAudioInputControl(
+                    AudioInputControl.AUDIO_INPUT_TYPE_STREAMING,
+                    AudioInputControl.GAIN_MODE_AUTOMATIC,
+                    AudioInputControl.AUDIO_INPUT_STATUS_ACTIVE));
+        }
+        when(mVolumeControlProfile.getAudioInputControlServices(mDevice)).thenReturn(controls);
+        return controls;
+    }
+
+    private List<AudioInputControl> prepareInactiveAmbientControls() {
+        List<AudioInputControl> controls = new ArrayList<>();
+        final int controlsCount = 2;
+        for (int i = 0; i < controlsCount; i++) {
+            controls.add(prepareAudioInputControl(
+                    AudioInputControl.AUDIO_INPUT_TYPE_STREAMING,
+                    AudioInputControl.GAIN_MODE_AUTOMATIC,
+                    AudioInputControl.AUDIO_INPUT_STATUS_INACTIVE));
+        }
+        when(mVolumeControlProfile.getAudioInputControlServices(mDevice)).thenReturn(controls);
+        return controls;
+    }
+
+    private AudioInputControl prepareAudioInputControl(int type, int mode, int status) {
+        AudioInputControl control = mock(AudioInputControl.class);
+        when(control.getAudioInputType()).thenReturn(type);
+        when(control.getGainMode()).thenReturn(mode);
+        when(control.getAudioInputStatus()).thenReturn(status);
+        return control;
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index 8cc9974..05f471f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -139,6 +139,11 @@
         mCachedDevice1 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1));
         mCachedDevice2 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2));
         mCachedDevice3 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice3));
+
+        mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mContext, mLocalBluetoothManager,
+                mCachedDeviceManager.mCachedDevices));
+        mCachedDeviceManager.mHearingAidDeviceManager = mHearingAidDeviceManager;
+        doNothing().when(mHearingAidDeviceManager).clearLocalDataIfNeeded(any());
     }
 
     /**
@@ -338,6 +343,8 @@
 
         // Call onDeviceUnpaired for the one in mCachedDevices.
         mCachedDeviceManager.onDeviceUnpaired(cachedDevice2);
+
+        verify(mHearingAidDeviceManager).clearLocalDataIfNeeded(cachedDevice2);
         verify(mDevice1).removeBond();
     }
 
@@ -353,6 +360,8 @@
 
         // Call onDeviceUnpaired for the one in mCachedDevices.
         mCachedDeviceManager.onDeviceUnpaired(cachedDevice1);
+
+        verify(mHearingAidDeviceManager).clearLocalDataIfNeeded(cachedDevice1);
         verify(mDevice2).removeBond();
     }
 
@@ -406,9 +415,6 @@
      */
     @Test
     public void updateHearingAidDevices_directToHearingAidDeviceManager() {
-        mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mContext, mLocalBluetoothManager,
-                mCachedDeviceManager.mCachedDevices));
-        mCachedDeviceManager.mHearingAidDeviceManager = mHearingAidDeviceManager;
         mCachedDeviceManager.updateHearingAidsDevices();
 
         verify(mHearingAidDeviceManager).updateHearingAidsDevices();
@@ -535,6 +541,7 @@
         // Call onDeviceUnpaired for the one in mCachedDevices.
         mCachedDeviceManager.onDeviceUnpaired(cachedDevice1);
 
+        verify(mHearingAidDeviceManager).clearLocalDataIfNeeded(cachedDevice1);
         verify(mDevice2).removeBond();
         assertThat(cachedDevice1.getGroupId()).isEqualTo(
                 BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
@@ -559,6 +566,7 @@
         // Call onDeviceUnpaired for the one in mCachedDevices.
         mCachedDeviceManager.onDeviceUnpaired(cachedDevice2);
 
+        verify(mHearingAidDeviceManager).clearLocalDataIfNeeded(cachedDevice2);
         verify(mDevice1).removeBond();
         assertThat(cachedDevice2.getGroupId()).isEqualTo(
                 BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
@@ -611,10 +619,7 @@
 
     @Test
     public void onActiveDeviceChanged_validHiSyncId_callExpectedFunction() {
-        mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mContext, mLocalBluetoothManager,
-                mCachedDeviceManager.mCachedDevices));
         doNothing().when(mHearingAidDeviceManager).onActiveDeviceChanged(any());
-        mCachedDeviceManager.mHearingAidDeviceManager = mHearingAidDeviceManager;
         when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
         CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1);
         cachedDevice1.setHearingAidInfo(
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index bf927a1..eb73eee 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -17,7 +17,6 @@
 
 import static android.bluetooth.BluetoothHearingAid.HI_SYNC_ID_INVALID;
 import static android.bluetooth.BluetoothLeAudio.AUDIO_LOCATION_FRONT_LEFT;
-import static android.bluetooth.BluetoothLeAudio.AUDIO_LOCATION_INVALID;
 
 import static com.android.settingslib.bluetooth.HapClientProfile.HearingAidType.TYPE_BINAURAL;
 import static com.android.settingslib.bluetooth.HapClientProfile.HearingAidType.TYPE_INVALID;
@@ -272,14 +271,14 @@
      *
      * Conditions:
      *      1) LeAudio hearing aid
-     *      2) Invalid audio location and device type
+     *      2) Invalid device type
      * Result:
      *      Do not set hearing aid info to the device.
      */
     @Test
     public void initHearingAidDeviceIfNeeded_leAudio_invalidInfo_notToSetHearingAidInfo() {
         when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
-        when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_INVALID);
+        when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_FRONT_LEFT);
         when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_INVALID);
 
         mHearingAidDeviceManager.initHearingAidDeviceIfNeeded(mCachedDevice1, null);
@@ -506,14 +505,14 @@
      *
      * Conditions:
      *      1) LeAudio hearing aid
-     *      2) Invalid audio location and device type
+     *      2) Invalid device type
      * Result:
      *      Do not set hearing aid info to the device.
      */
     @Test
     public void updateHearingAidsDevices_leAudio_invalidInfo_notToSetHearingAidInfo() {
         when(mCachedDevice1.getProfiles()).thenReturn(List.of(mLeAudioProfile, mHapClientProfile));
-        when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_INVALID);
+        when(mLeAudioProfile.getAudioLocation(mDevice1)).thenReturn(AUDIO_LOCATION_FRONT_LEFT);
         when(mHapClientProfile.getHearingAidType(mDevice1)).thenReturn(TYPE_INVALID);
         mCachedDeviceManager.mCachedDevices.add(mCachedDevice1);
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java
index b659c02..6d83588 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingDeviceLocalDataManagerTest.java
@@ -194,6 +194,19 @@
         verify(mListener).onDeviceLocalDataChange(TEST_ADDRESS, newData);
     }
 
+    @Test
+    public void clear_dataIsRemoved() {
+        String settings = Settings.Global.getStringForUser(mContext.getContentResolver(),
+                Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, UserHandle.USER_SYSTEM);
+        assertThat(settings.contains(TEST_ADDRESS)).isTrue();
+
+        HearingDeviceLocalDataManager.clear(mContext, mDevice);
+
+        settings = Settings.Global.getStringForUser(mContext.getContentResolver(),
+                Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, UserHandle.USER_SYSTEM);
+        assertThat(settings.contains(TEST_ADDRESS)).isFalse();
+    }
+
     private void prepareTestDataInSettings() {
         String data = generateSettingsString(TEST_ADDRESS, TEST_AMBIENT, TEST_GROUP_AMBIENT,
                 TEST_AMBIENT_CONTROL_EXPANDED);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index aca2c4e..91ac34a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -72,7 +72,6 @@
 public final class DeviceConfigService extends Binder {
     private static final List<String> sAconfigTextProtoFilesOnDevice = List.of(
             "/system/etc/aconfig_flags.pb",
-            "/system_ext/etc/aconfig_flags.pb",
             "/product/etc/aconfig_flags.pb",
             "/vendor/etc/aconfig_flags.pb");
 
@@ -133,12 +132,7 @@
             }
 
             pw.println("DeviceConfig provider: ");
-            try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd)) {
-                DeviceConfig.dump(pfd, pw, /* prefix= */ "  ", args);
-            } catch (IOException e) {
-                pw.print("IOException creating ParcelFileDescriptor: ");
-                pw.println(e);
-            }
+            DeviceConfig.dump(pw, /* prefix= */ "  ", args);
         }
 
         IContentProvider iprovider = mProvider.getIContentProvider();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 7aed615..5cd534e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -171,7 +171,6 @@
 
     private static final List<String> sAconfigTextProtoFilesOnDevice = List.of(
             "/system/etc/aconfig_flags.pb",
-            "/system_ext/etc/aconfig_flags.pb",
             "/product/etc/aconfig_flags.pb",
             "/vendor/etc/aconfig_flags.pb");
 
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
index 9fe85b7..029b9cd 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
@@ -38,6 +38,8 @@
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.PointerInputScope
 import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
+import androidx.compose.ui.input.pointer.changedToDownIgnoreConsumed
+import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
 import androidx.compose.ui.input.pointer.positionChange
 import androidx.compose.ui.input.pointer.util.VelocityTracker
 import androidx.compose.ui.input.pointer.util.addPointerInputChange
@@ -50,6 +52,7 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.util.fastAny
+import androidx.compose.ui.util.fastSumBy
 import com.android.compose.modifiers.thenIf
 import kotlin.math.sign
 import kotlinx.coroutines.CoroutineScope
@@ -65,9 +68,10 @@
 interface NestedDraggable {
     /**
      * Called when a drag is started in the given [position] (*before* dragging the touch slop) and
-     * in the direction given by [sign].
+     * in the direction given by [sign], with the given number of [pointersDown] when the touch slop
+     * was detected.
      */
-    fun onDragStarted(position: Offset, sign: Float): Controller
+    fun onDragStarted(position: Offset, sign: Float, pointersDown: Int): Controller
 
     /**
      * Whether this draggable should consume any scroll amount with the given [sign] coming from a
@@ -170,6 +174,9 @@
      */
     private var lastFirstDown: Offset? = null
 
+    /** The number of pointers down. */
+    private var pointersDownCount = 0
+
     init {
         delegate(nestedScrollModifierNode(this, nestedScrollDispatcher))
     }
@@ -234,6 +241,11 @@
 
         awaitEachGesture {
             val down = awaitFirstDown(requireUnconsumed = false)
+            check(down.position == lastFirstDown) {
+                "Position from detectDrags() is not the same as position in trackDownPosition()"
+            }
+            check(pointersDownCount == 1) { "pointersDownCount is equal to $pointersDownCount" }
+
             var overSlop = 0f
             val onTouchSlopReached = { change: PointerInputChange, over: Float ->
                 change.consume()
@@ -276,10 +288,13 @@
 
             if (drag != null) {
                 velocityTracker.resetTracking()
-
                 val sign = (drag.position - down.position).toFloat().sign
+                check(pointersDownCount > 0) { "pointersDownCount is equal to $pointersDownCount" }
                 val wrappedController =
-                    WrappedController(coroutineScope, draggable.onDragStarted(down.position, sign))
+                    WrappedController(
+                        coroutineScope,
+                        draggable.onDragStarted(down.position, sign, pointersDownCount),
+                    )
                 if (overSlop != 0f) {
                     onDrag(wrappedController, drag, overSlop, velocityTracker)
                 }
@@ -424,7 +439,22 @@
      */
 
     private suspend fun PointerInputScope.trackDownPosition() {
-        awaitEachGesture { lastFirstDown = awaitFirstDown(requireUnconsumed = false).position }
+        awaitEachGesture {
+            val down = awaitFirstDown(requireUnconsumed = false)
+            lastFirstDown = down.position
+            pointersDownCount = 1
+
+            do {
+                pointersDownCount +=
+                    awaitPointerEvent().changes.fastSumBy { change ->
+                        when {
+                            change.changedToDownIgnoreConsumed() -> 1
+                            change.changedToUpIgnoreConsumed() -> -1
+                            else -> 0
+                        }
+                    }
+            } while (pointersDownCount > 0)
+        }
     }
 
     override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
@@ -451,8 +481,14 @@
         val sign = offset.sign
         if (nestedScrollController == null && draggable.shouldConsumeNestedScroll(sign)) {
             val startedPosition = checkNotNull(lastFirstDown) { "lastFirstDown is not set" }
+
+            // TODO(b/382665591): Replace this by check(pointersDownCount > 0).
+            val pointersDown = pointersDownCount.coerceAtLeast(1)
             nestedScrollController =
-                WrappedController(coroutineScope, draggable.onDragStarted(startedPosition, sign))
+                WrappedController(
+                    coroutineScope,
+                    draggable.onDragStarted(startedPosition, sign, pointersDown),
+                )
         }
 
         val controller = nestedScrollController ?: return Offset.Zero
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
index fd3902f..735ab68 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
@@ -41,6 +41,7 @@
 import androidx.compose.ui.test.swipeLeft
 import androidx.compose.ui.unit.Velocity
 import com.google.common.truth.Truth.assertThat
+import kotlin.math.ceil
 import kotlinx.coroutines.awaitCancellation
 import org.junit.Ignore
 import org.junit.Rule
@@ -383,6 +384,79 @@
         assertThat(draggable.onDragStoppedCalled).isTrue()
     }
 
+    @Test
+    fun pointersDown() {
+        val draggable = TestDraggable()
+        val touchSlop =
+            rule.setContentWithTouchSlop {
+                Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation))
+            }
+
+        (1..5).forEach { nDown ->
+            rule.onRoot().performTouchInput {
+                repeat(nDown) { pointerId -> down(pointerId, center) }
+
+                moveBy(pointerId = 0, touchSlop.toOffset())
+            }
+
+            assertThat(draggable.onDragStartedPointersDown).isEqualTo(nDown)
+
+            rule.onRoot().performTouchInput {
+                repeat(nDown) { pointerId -> up(pointerId = pointerId) }
+            }
+        }
+    }
+
+    @Test
+    fun pointersDown_nestedScroll() {
+        val draggable = TestDraggable()
+        val touchSlop =
+            rule.setContentWithTouchSlop {
+                Box(
+                    Modifier.fillMaxSize()
+                        .nestedDraggable(draggable, orientation)
+                        .nestedScrollable(rememberScrollState())
+                )
+            }
+
+        (1..5).forEach { nDown ->
+            rule.onRoot().performTouchInput {
+                repeat(nDown) { pointerId -> down(pointerId, center) }
+
+                moveBy(pointerId = 0, (touchSlop + 1f).toOffset())
+            }
+
+            assertThat(draggable.onDragStartedPointersDown).isEqualTo(nDown)
+
+            rule.onRoot().performTouchInput {
+                repeat(nDown) { pointerId -> up(pointerId = pointerId) }
+            }
+        }
+    }
+
+    @Test
+    fun pointersDown_downThenUpThenDown() {
+        val draggable = TestDraggable()
+        val touchSlop =
+            rule.setContentWithTouchSlop {
+                Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation))
+            }
+
+        val slopThird = ceil(touchSlop / 3f).toOffset()
+        rule.onRoot().performTouchInput {
+            repeat(5) { down(pointerId = it, center) } // + 5
+            moveBy(pointerId = 0, slopThird)
+
+            listOf(2, 3).forEach { up(pointerId = it) } // - 2
+            moveBy(pointerId = 0, slopThird)
+
+            listOf(5, 6, 7).forEach { down(pointerId = it, center) } // + 3
+            moveBy(pointerId = 0, slopThird)
+        }
+
+        assertThat(draggable.onDragStartedPointersDown).isEqualTo(6)
+    }
+
     private fun ComposeContentTestRule.setContentWithTouchSlop(
         content: @Composable () -> Unit
     ): Float {
@@ -413,12 +487,18 @@
 
         var onDragStartedPosition = Offset.Zero
         var onDragStartedSign = 0f
+        var onDragStartedPointersDown = 0
         var onDragDelta = 0f
 
-        override fun onDragStarted(position: Offset, sign: Float): NestedDraggable.Controller {
+        override fun onDragStarted(
+            position: Offset,
+            sign: Float,
+            pointersDown: Int,
+        ): NestedDraggable.Controller {
             onDragStartedCalled = true
             onDragStartedPosition = position
             onDragStartedSign = sign
+            onDragStartedPointersDown = pointersDown
             onDragDelta = 0f
 
             onDragStarted.invoke(position, sign)
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index 35cdf81..59d0b55 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -337,13 +337,6 @@
         check(!isAnimatingOffset()) { "SwipeAnimation.animateOffset() can only be called once" }
 
         val initialProgress = progress
-        // Skip the animation if we have already reached the target content and the overscroll does
-        // not animate anything.
-        val hasReachedTargetContent =
-            (targetContent == toContent && initialProgress >= 1f) ||
-                (targetContent == fromContent && initialProgress <= 0f)
-        val skipAnimation =
-            hasReachedTargetContent && !contentTransition.isWithinProgressRange(initialProgress)
 
         val targetContent =
             if (targetContent != currentContent && !canChangeContent(targetContent)) {
@@ -352,6 +345,14 @@
                 targetContent
             }
 
+        // Skip the animation if we have already reached the target content and the overscroll does
+        // not animate anything.
+        val hasReachedTargetContent =
+            (targetContent == toContent && initialProgress >= 1f) ||
+                (targetContent == fromContent && initialProgress <= 0f)
+        val skipAnimation =
+            hasReachedTargetContent && !contentTransition.isWithinProgressRange(initialProgress)
+
         val targetOffset =
             if (targetContent == fromContent) {
                 0f
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 2c8dc32..b20056d 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -42,7 +42,6 @@
 import com.android.compose.animation.scene.subjects.assertThat
 import com.android.compose.test.MonotonicClockTestScope
 import com.android.compose.test.runMonotonicClockTest
-import com.android.compose.test.transition
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Deferred
@@ -921,6 +920,28 @@
     }
 
     @Test
+    fun blockTransition_animated() = runGestureTest {
+        assertIdle(SceneA)
+        layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) }
+
+        // Swipe up to scene B. Overscroll 50%.
+        val dragController = onDragStarted(overSlop = up(1.5f), expectedConsumedOverSlop = up(1.0f))
+        assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneB, progress = 1f)
+
+        // Block the transition when the user release their finger.
+        canChangeScene = { false }
+        val velocityConsumed =
+            dragController.onDragStoppedAnimateLater(velocity = -velocityThreshold)
+
+        // Start an animation: overscroll and from 1f to 0f.
+        assertTransition(currentScene = SceneA, fromScene = SceneA, toScene = SceneB, progress = 1f)
+
+        val consumed = velocityConsumed.await()
+        assertThat(consumed).isEqualTo(-velocityThreshold)
+        assertIdle(SceneA)
+    }
+
+    @Test
     fun scrollFromIdleWithNoTargetScene_shouldUseOverscrollSpecIfAvailable() = runGestureTest {
         layoutState.transitions = transitions {
             overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index d75c013..ab93659 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -1589,7 +1589,8 @@
             val logoInfo by collectLastValue(kosmos.promptViewModel.logoInfo)
             assertThat(logoInfo).isNotNull()
             assertThat(logoInfo!!.first).isEqualTo(defaultLogoIconWithBadge)
-            assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionWithBadge)
+            // Logo label does not use badge info.
+            assertThat(logoInfo!!.second).isEqualTo(defaultLogoDescriptionFromAppInfo)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/OWNERS b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/OWNERS
new file mode 100644
index 0000000..2355c48
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1562219
+chrisgollner@google.com
+jmokut@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
index 969dace..7dc7016 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/source/TestShortcuts.kt
@@ -40,7 +40,6 @@
 import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.System
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
@@ -553,18 +552,8 @@
             simpleShortcutCategory(
                 MultiTasking,
                 "Split screen",
-                "Switch to app on left or above while using split screen",
-            ),
-            simpleShortcutCategory(
-                MultiTasking,
-                "Split screen",
                 "Use split screen with app on the right",
             ),
-            simpleShortcutCategory(
-                MultiTasking,
-                "Split screen",
-                "Switch to app on right or below while using split screen",
-            ),
             simpleShortcutCategory(System, "System controls", "Show shortcuts"),
             simpleShortcutCategory(System, "System controls", "View recent apps"),
         )
@@ -595,15 +584,9 @@
                 keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT
             ),
             simpleInputGestureData(
-                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT
-            ),
-            simpleInputGestureData(
                 keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT
             ),
             simpleInputGestureData(
-                keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT
-            ),
-            simpleInputGestureData(
                 keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER
             ),
             simpleInputGestureData(keyGestureType = KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
index 5e9badc..4dbe7c8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -171,8 +172,10 @@
             // WHEN transition is cancelled
             repository.sendTransitionStep(step(.1f, TransitionState.CANCELED))
 
-            // THEN alpha is immediately set to 1f (expected lockscreen alpha state)
-            assertThat(deviceEntryBackgroundViewAlpha).isEqualTo(1f)
+            // THEN alpha updates according to whether the scene framework is enabled (CANCELED is
+            // ignored when the scene framework is enabled).
+            assertThat(deviceEntryBackgroundViewAlpha)
+                .isEqualTo(if (SceneContainerFlag.isEnabled) 0f else 1f)
         }
 
     @Test
@@ -195,14 +198,14 @@
 
     private fun step(
         value: Float,
-        state: TransitionState = TransitionState.RUNNING
+        state: TransitionState = TransitionState.RUNNING,
     ): TransitionStep {
         return TransitionStep(
             from = KeyguardState.AOD,
             to = KeyguardState.LOCKSCREEN,
             value = value,
             transitionState = state,
-            ownerName = "AodToLockscreenTransitionViewModelTest"
+            ownerName = "AodToLockscreenTransitionViewModelTest",
         )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index d0da2e9..576795d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.ShadeTestUtil
 import com.android.systemui.shade.shadeTestUtil
 import com.android.systemui.testKosmos
@@ -178,11 +179,13 @@
                     ),
                 testScope = testScope,
             )
-            assertThat(values.size).isEqualTo(3)
+            assertThat(values.size).isEqualTo(if (SceneContainerFlag.isEnabled) 2 else 3)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
 
-            // Cancel will reset the translation
-            assertThat(values[2]).isEqualTo(0)
+            // When the scene framework is not enabled, cancel will reset the translation
+            if (!SceneContainerFlag.isEnabled) {
+                assertThat(values.last()).isEqualTo(0f)
+            }
         }
 
     @Test
@@ -242,8 +245,9 @@
             // WHEN transition is canceled
             repository.sendTransitionStep(step(1f, TransitionState.CANCELED))
 
-            // THEN alpha is immediately set to 0f
-            assertThat(actual).isEqualTo(0f)
+            // THEN alpha updates according to whether the scene framework is enabled (CANCELED is
+            // ignored when the scene framework is enabled).
+            assertThat(actual).isEqualTo(if (SceneContainerFlag.isEnabled) 1f else 0f)
         }
 
     private fun step(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
index ae7c44e..8b9ae9a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
@@ -39,7 +39,7 @@
 
     @Test
     fun startDrag_listHasSpacers() {
-        underTest.onStarted(TestEditTiles[0])
+        underTest.onStarted(TestEditTiles[0], DragType.Add)
 
         // [ a ] [ b ] [ c ] [ X ]
         // [ Large D ] [ e ] [ X ]
@@ -51,8 +51,8 @@
 
     @Test
     fun moveDrag_listChanges() {
-        underTest.onStarted(TestEditTiles[4])
-        underTest.onMoved(3, false)
+        underTest.onStarted(TestEditTiles[4], DragType.Add)
+        underTest.onTargeting(3, false)
 
         // Tile E goes to index 3
         // [ a ] [ b ] [ c ] [ e ]
@@ -65,8 +65,8 @@
     fun moveDragOnSidesOfLargeTile_listChanges() {
         val draggedCell = TestEditTiles[4]
 
-        underTest.onStarted(draggedCell)
-        underTest.onMoved(4, true)
+        underTest.onStarted(draggedCell, DragType.Add)
+        underTest.onTargeting(4, true)
 
         // Tile E goes to the right side of tile D, list is unchanged
         // [ a ] [ b ] [ c ] [ X ]
@@ -74,7 +74,7 @@
         assertThat(underTest.tiles.toStrings())
             .isEqualTo(listOf("a", "b", "c", "spacer", "d", "e", "spacer"))
 
-        underTest.onMoved(4, false)
+        underTest.onTargeting(4, false)
 
         // Tile E goes to the left side of tile D, they swap positions
         // [ a ] [ b ] [ c ] [ e ]
@@ -87,8 +87,8 @@
     fun moveNewTile_tileIsAdded() {
         val newTile = createEditTile("newTile", 2)
 
-        underTest.onStarted(newTile)
-        underTest.onMoved(5, false)
+        underTest.onStarted(newTile, DragType.Add)
+        underTest.onTargeting(5, false)
 
         // New tile goes to index 5
         // [ a ] [ b ] [ c ] [ X ]
@@ -102,7 +102,7 @@
 
     @Test
     fun movedTileOutOfBounds_tileDisappears() {
-        underTest.onStarted(TestEditTiles[0])
+        underTest.onStarted(TestEditTiles[0], DragType.Add)
         underTest.movedOutOfBounds()
 
         assertThat(underTest.tiles.toStrings()).doesNotContain(TestEditTiles[0].tile.tileSpec.spec)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt
index 583db72..bbfa7e7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt
@@ -21,6 +21,7 @@
 import android.graphics.drawable.TestStubDrawable
 import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLoggerFake
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription
@@ -29,9 +30,12 @@
 import com.android.systemui.common.ui.compose.toAnnotatedString
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.qs.FakeQSFactory
 import com.android.systemui.qs.FakeQSTile
+import com.android.systemui.qs.QSEditEvent
 import com.android.systemui.qs.panels.data.repository.stockTilesRepository
 import com.android.systemui.qs.panels.domain.interactor.FakeTileAvailabilityInteractor
 import com.android.systemui.qs.panels.domain.interactor.tileAvailabilityInteractorsMap
@@ -42,8 +46,10 @@
 import com.android.systemui.qs.pipeline.data.repository.fakeMinimumTilesRepository
 import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.metricSpec
 import com.android.systemui.qs.qsTileFactory
 import com.android.systemui.qs.shared.model.TileCategory
+import com.android.systemui.qs.tiles.impl.airplane.qsAirplaneModeTileConfig
 import com.android.systemui.qs.tiles.impl.alarm.qsAlarmTileConfig
 import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig
 import com.android.systemui.qs.tiles.impl.flashlight.qsFlashlightTileConfig
@@ -86,6 +92,7 @@
                 qsFlashlightTileConfig,
                 qsBatterySaverTileConfig,
                 qsAlarmTileConfig,
+                qsAirplaneModeTileConfig,
                 qsCameraSensorPrivacyToggleTileConfig,
                 qsMicrophoneSensorPrivacyToggleTileConfig,
             )
@@ -116,7 +123,7 @@
 
             fakeInstalledTilesRepository.setInstalledServicesForUser(
                 userTracker.userId,
-                listOf(serviceInfo1, serviceInfo2)
+                listOf(serviceInfo1, serviceInfo2),
             )
 
             with(fakeQSTileConfigProvider) { configs.forEach { putConfig(it.tileSpec, it) } }
@@ -424,10 +431,7 @@
             testScope.runTest {
                 val tiles by collectLastValue(underTest.tiles)
                 val currentTiles =
-                    mutableListOf(
-                        TileSpec.create("flashlight"),
-                        TileSpec.create("airplane"),
-                    )
+                    mutableListOf(TileSpec.create("flashlight"), TileSpec.create("airplane"))
                 currentTilesInteractor.setTiles(currentTiles)
                 assertThat(currentTiles.size).isLessThan(minNumberOfTiles)
 
@@ -549,6 +553,156 @@
             }
         }
 
+    // UI EVENT TESTS
+
+    @Test
+    fun startEditing_onlyOneEvent() =
+        kosmos.runTest {
+            underTest.startEditing()
+            underTest.startEditing()
+
+            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+
+            assertThat(uiEventLoggerFake[0].eventId).isEqualTo(QSEditEvent.QS_EDIT_OPEN.id)
+        }
+
+    @Test
+    fun stopEditing_notEditing_noEvent() =
+        kosmos.runTest {
+            underTest.stopEditing()
+
+            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0)
+        }
+
+    @Test
+    fun stopEditing_whenEditing_correctEvent() =
+        kosmos.runTest {
+            underTest.startEditing()
+            underTest.stopEditing()
+
+            assertThat(uiEventLoggerFake[1].eventId).isEqualTo(QSEditEvent.QS_EDIT_CLOSED.id)
+        }
+
+    @Test
+    fun addTile_correctPackageAndPosition() =
+        kosmos.runTest {
+            val flashlightTile = TileSpec.create("flashlight")
+            val airplaneTile = TileSpec.create("airplane")
+            val internetTile = TileSpec.create("internet")
+            val customTile = TileSpec.create(component2)
+            currentTilesInteractor.setTiles(listOf(flashlightTile))
+            runCurrent()
+
+            underTest.addTile(airplaneTile)
+            underTest.addTile(internetTile, position = 0)
+            underTest.addTile(customTile, position = 1)
+
+            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(3)
+
+            with(uiEventLoggerFake[0]) {
+                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_ADD.id)
+                assertThat(packageName).isEqualTo(airplaneTile.metricSpec)
+                assertThat(position).isEqualTo(-1)
+            }
+            with(uiEventLoggerFake[1]) {
+                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_ADD.id)
+                assertThat(packageName).isEqualTo(internetTile.metricSpec)
+                assertThat(position).isEqualTo(0)
+            }
+            with(uiEventLoggerFake[2]) {
+                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_ADD.id)
+                assertThat(packageName).isEqualTo(customTile.metricSpec)
+                assertThat(position).isEqualTo(1)
+            }
+        }
+
+    @Test
+    fun addTile_alreadyThere_usesMoveEvent() =
+        kosmos.runTest {
+            val flashlightTile = TileSpec.create("flashlight")
+            val airplaneTile = TileSpec.create("airplane")
+            val internetTile = TileSpec.create("internet")
+            currentTilesInteractor.setTiles(listOf(flashlightTile, airplaneTile, internetTile))
+            runCurrent()
+
+            underTest.addTile(flashlightTile) // adding at the end, should use correct position
+            underTest.addTile(internetTile, 0)
+
+            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(2)
+
+            with(uiEventLoggerFake[0]) {
+                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_MOVE.id)
+                assertThat(packageName).isEqualTo(flashlightTile.metricSpec)
+                // adding at the end, should use correct position
+                assertThat(position).isEqualTo(2)
+            }
+            with(uiEventLoggerFake[1]) {
+                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_MOVE.id)
+                assertThat(packageName).isEqualTo(internetTile.metricSpec)
+                assertThat(position).isEqualTo(0)
+            }
+        }
+
+    @Test
+    fun removeTileEvent() =
+        kosmos.runTest {
+            val flashlightTile = TileSpec.create("flashlight")
+            val airplaneTile = TileSpec.create("airplane")
+            val internetTile = TileSpec.create("internet")
+            currentTilesInteractor.setTiles(listOf(flashlightTile, airplaneTile, internetTile))
+            runCurrent()
+
+            underTest.removeTile(airplaneTile)
+
+            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+
+            with(uiEventLoggerFake[0]) {
+                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_REMOVE.id)
+                assertThat(packageName).isEqualTo(airplaneTile.metricSpec)
+            }
+        }
+
+    @Test
+    fun setTiles_emitsCorrectOperation_individualOperations() =
+        kosmos.runTest {
+            val flashlightTile = TileSpec.create("flashlight")
+            val airplaneTile = TileSpec.create("airplane")
+            val internetTile = TileSpec.create("internet")
+            val alarmTile = TileSpec.create("alarm")
+
+            currentTilesInteractor.setTiles(listOf(flashlightTile, airplaneTile, internetTile))
+            runCurrent()
+
+            // 0. Move flashlightTile to position 2
+            underTest.setTiles(listOf(airplaneTile, internetTile, flashlightTile))
+            runCurrent()
+
+            // 1. Add alarm tile at position 1
+            underTest.setTiles(listOf(airplaneTile, alarmTile, internetTile, flashlightTile))
+            runCurrent()
+
+            // 2. Remove internetTile
+            underTest.setTiles(listOf(airplaneTile, alarmTile, flashlightTile))
+            runCurrent()
+
+            assertThat(uiEventLoggerFake.numLogs()).isEqualTo(3)
+
+            with(uiEventLoggerFake[0]) {
+                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_MOVE.id)
+                assertThat(packageName).isEqualTo(flashlightTile.metricSpec)
+                assertThat(position).isEqualTo(2)
+            }
+            with(uiEventLoggerFake[1]) {
+                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_ADD.id)
+                assertThat(packageName).isEqualTo(alarmTile.metricSpec)
+                assertThat(position).isEqualTo(1)
+            }
+            with(uiEventLoggerFake[2]) {
+                assertThat(eventId).isEqualTo(QSEditEvent.QS_EDIT_REMOVE.id)
+                assertThat(packageName).isEqualTo(internetTile.metricSpec)
+            }
+        }
+
     companion object {
         private val drawable1 = TestStubDrawable("drawable1")
         private val appName1 = "App1"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
index 869ab6c..1fc1c0f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
@@ -85,6 +85,24 @@
         assertThat(TileSpec.create("")).isEqualTo(TileSpec.Invalid)
     }
 
+    @Test
+    fun metricSpec_invalid() {
+        assertThat(TileSpec.Invalid.metricSpec).isEmpty()
+    }
+
+    @Test
+    fun metricSpec_platform_specName() {
+        val tile = "spec"
+        assertThat(TileSpec.create(tile).metricSpec).isEqualTo(tile)
+    }
+
+    @Test
+    fun metricSpec_custom_packageName() {
+        val componentName = ComponentName("test_pkg", "test_cls")
+
+        assertThat(TileSpec.create(componentName).metricSpec).isEqualTo(componentName.packageName)
+    }
+
     companion object {
         private const val CUSTOM_TILE_PREFIX = "custom("
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
index a8d5c31..e93d0ef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorTest.kt
@@ -16,32 +16,26 @@
 
 package com.android.systemui.shade.domain.interactor
 
-import android.content.mockedContext
 import android.content.res.Configuration
 import android.content.res.mockResources
 import android.view.Display
-import android.view.mockWindowManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.kosmos.testScope
 import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.scene.ui.view.mockShadeRootView
 import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
 import com.android.systemui.testKosmos
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.advanceUntilIdle
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
 import org.mockito.kotlin.any
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
-import org.mockito.kotlin.verifyNoMoreInteractions
 import org.mockito.kotlin.whenever
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class ShadeDisplaysInteractorTest : SysuiTestCase() {
@@ -49,9 +43,7 @@
 
     private val shadeRootview = kosmos.mockShadeRootView
     private val positionRepository = kosmos.fakeShadeDisplaysRepository
-    private val shadeContext = kosmos.mockedContext
-    private val testScope = kosmos.testScope
-    private val shadeWm = kosmos.mockWindowManager
+    private val shadeContext = kosmos.mockedWindowContext
     private val resources = kosmos.mockResources
     private val configuration = mock<Configuration>()
     private val display = mock<Display>()
@@ -66,8 +58,8 @@
         whenever(resources.configuration).thenReturn(configuration)
 
         whenever(shadeContext.displayId).thenReturn(0)
-        whenever(shadeContext.getSystemService(any())).thenReturn(shadeWm)
         whenever(shadeContext.resources).thenReturn(resources)
+        whenever(shadeContext.display).thenReturn(display)
     }
 
     @Test
@@ -77,7 +69,7 @@
 
         underTest.start()
 
-        verifyNoMoreInteractions(shadeWm)
+        verify(shadeContext, never()).reparentToDisplay(any())
     }
 
     @Test
@@ -87,24 +79,6 @@
 
         underTest.start()
 
-        inOrder(shadeWm).apply {
-            verify(shadeWm).removeView(eq(shadeRootview))
-            verify(shadeWm).addView(eq(shadeRootview), any())
-        }
-    }
-
-    @Test
-    fun start_shadePositionChanges_removedThenAdded() {
-        whenever(display.displayId).thenReturn(0)
-        positionRepository.setDisplayId(0)
-        underTest.start()
-
-        positionRepository.setDisplayId(1)
-        testScope.advanceUntilIdle()
-
-        inOrder(shadeWm).apply {
-            verify(shadeWm).removeView(eq(shadeRootview))
-            verify(shadeWm).addView(eq(shadeRootview), any())
-        }
+        verify(shadeContext).reparentToDisplay(eq(1))
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index c9ca67e..615f4b01 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -78,7 +78,7 @@
     public void setUp() {
         if (NotifRedesignFooter.isEnabled()) {
             mView = (FooterView) LayoutInflater.from(mSpyContext).inflate(
-                    R.layout.status_bar_notification_footer_redesign, null, false);
+                    R.layout.notification_2025_footer, null, false);
         } else {
             mView = (FooterView) LayoutInflater.from(mSpyContext).inflate(
                     R.layout.status_bar_notification_footer, null, false);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index 46c360a..be20bc1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -225,16 +225,12 @@
             val displayId = 123
             darkIconRepository.darkState(displayId).value =
                 SysuiDarkIconDispatcher.DarkChange(emptyList(), 0f, 0xAABBCC)
-            val iconColorsLookup by collectLastValue(underTest.iconColors(displayId))
-            assertThat(iconColorsLookup).isNotNull()
-
-            val iconColors = iconColorsLookup?.iconColors(Rect())
+            val iconColors by collectLastValue(underTest.iconColors(displayId))
             assertThat(iconColors).isNotNull()
-            iconColors!!
 
-            assertThat(iconColors.tint).isEqualTo(0xAABBCC)
+            assertThat(iconColors!!.tint).isEqualTo(0xAABBCC)
 
-            val staticDrawableColor = iconColors.staticDrawableColor(Rect())
+            val staticDrawableColor = iconColors!!.staticDrawableColor(Rect())
 
             assertThat(staticDrawableColor).isEqualTo(0xAABBCC)
         }
@@ -245,8 +241,7 @@
             val displayId = 321
             darkIconRepository.darkState(displayId).value =
                 SysuiDarkIconDispatcher.DarkChange(listOf(Rect(0, 0, 5, 5)), 0f, 0xAABBCC)
-            val iconColorsLookup by collectLastValue(underTest.iconColors(displayId))
-            val iconColors = iconColorsLookup?.iconColors(Rect(1, 1, 4, 4))
+            val iconColors by collectLastValue(underTest.iconColors(displayId))
             val staticDrawableColor = iconColors?.staticDrawableColor(Rect(6, 6, 7, 7))
             assertThat(staticDrawableColor).isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT)
         }
@@ -257,9 +252,9 @@
             val displayId = 987
             darkIconRepository.darkState(displayId).value =
                 SysuiDarkIconDispatcher.DarkChange(listOf(Rect(0, 0, 5, 5)), 0f, 0xAABBCC)
-            val iconColorsLookup by collectLastValue(underTest.iconColors(displayId))
-            val iconColors = iconColorsLookup?.iconColors(Rect(6, 6, 7, 7))
-            assertThat(iconColors).isNull()
+            val iconColors by collectLastValue(underTest.iconColors(displayId))
+            assertThat(iconColors!!.staticDrawableColor(Rect(6, 6, 7, 7)))
+                .isEqualTo(DarkIconDispatcher.DEFAULT_ICON_TINT)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/view/ViewUtilTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/view/ViewUtilTest.kt
index 3dcb828..72527dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/view/ViewUtilTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/view/ViewUtilTest.kt
@@ -105,6 +105,20 @@
         assertThat(outRect.top).isEqualTo(VIEW_TOP)
         assertThat(outRect.bottom).isEqualTo(VIEW_BOTTOM)
     }
+
+    @Test
+    fun viewBoundsOnScreen_viewAnchoredAtOriginInWindow() {
+        // view is anchored at 0,0 in its window
+        view.setLeftTopRightBottom(0, 0, VIEW_RIGHT - VIEW_LEFT, VIEW_BOTTOM - VIEW_TOP)
+
+        val outRect = Rect()
+        view.viewBoundsOnScreen(outRect)
+
+        assertThat(outRect.left).isEqualTo(VIEW_LEFT)
+        assertThat(outRect.right).isEqualTo(VIEW_RIGHT)
+        assertThat(outRect.top).isEqualTo(VIEW_TOP)
+        assertThat(outRect.bottom).isEqualTo(VIEW_BOTTOM)
+    }
 }
 
 private const val VIEW_LEFT = 30
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer_redesign.xml b/packages/SystemUI/res/layout/notification_2025_footer.xml
similarity index 97%
rename from packages/SystemUI/res/layout/status_bar_notification_footer_redesign.xml
rename to packages/SystemUI/res/layout/notification_2025_footer.xml
index 71c77a5..9b3d67f 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer_redesign.xml
+++ b/packages/SystemUI/res/layout/notification_2025_footer.xml
@@ -64,6 +64,8 @@
                 android:contentDescription="@string/accessibility_clear_all"
                 android:focusable="true"
                 android:text="@string/clear_all_notifications_text"
+                android:ellipsize="end"
+                android:maxLines="1"
                 app:layout_constraintEnd_toStartOf="@id/settings_button"
                 app:layout_constraintStart_toEndOf="@id/history_button" />
 
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index b47aa2c..12f6e69 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -183,30 +183,9 @@
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
 
-    <style name="TextAppearance.AuthCredential.OldTitle">
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-        <item name="android:paddingTop">12dp</item>
-        <item name="android:paddingHorizontal">24dp</item>
-        <item name="android:textSize">24sp</item>
-    </style>
-
-    <style name="TextAppearance.AuthCredential.OldSubtitle">
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-        <item name="android:paddingTop">8dp</item>
-        <item name="android:paddingHorizontal">24dp</item>
-        <item name="android:textSize">16sp</item>
-    </style>
-
-    <style name="TextAppearance.AuthCredential.OldDescription">
-        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-        <item name="android:paddingTop">8dp</item>
-        <item name="android:paddingHorizontal">24dp</item>
-        <item name="android:textSize">14sp</item>
-    </style>
-
     <style name="TextAppearance.AuthCredential.LogoDescription" parent="TextAppearance.Material3.LabelLarge" >
         <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
-        <item name="android:gravity">@integer/biometric_dialog_text_gravity</item>
+        <item name="android:gravity">center_horizontal</item>
         <item name="android:maxLines">1</item>
         <item name="android:textColor">@androidprv:color/materialColorOnSurfaceVariant</item>
         <item name="android:ellipsize">end</item>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 9e8cabf..8576a6e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -330,6 +330,11 @@
     }
 
     @Override
+    public int hashCode() {
+        return key.hashCode();
+    }
+
+    @Override
     public String toString() {
         return "[" + key.toString() + "] " + title;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index df34952..4dcf268 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -982,8 +982,9 @@
     activityTaskManager: ActivityTaskManager,
 ): Pair<Drawable?, String> {
     // If the app sets customized icon/description, use the passed-in value directly
-    var icon: Drawable? =
-        if (prompt.logoBitmap != null) BitmapDrawable(resources, prompt.logoBitmap) else null
+    val customizedIcon: Drawable? =
+        prompt.logoBitmap?.let { BitmapDrawable(resources, prompt.logoBitmap) }
+    var icon = customizedIcon
     var label = prompt.logoDescription ?: ""
     if (icon != null && label.isNotEmpty()) {
         return Pair(icon, label)
@@ -1009,12 +1010,11 @@
         }
     }
 
-    // Add user badge
+    // Add user badge for non-customized logo icon
     val userHandle = UserHandle.of(prompt.userInfo.userId)
-    if (label.isNotEmpty()) {
-        label = packageManager.getUserBadgedLabel(label, userHandle).toString()
+    if (icon != null && icon != customizedIcon) {
+        icon = packageManager.getUserBadgedIcon(icon, userHandle)
     }
-    icon = icon?.let { packageManager.getUserBadgedIcon(it, userHandle) }
 
     return Pair(icon, label)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/OWNERS b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/OWNERS
new file mode 100644
index 0000000..2355c48
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1562219
+chrisgollner@google.com
+jmokut@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
index 30a2f33..d7be5e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
@@ -136,10 +136,6 @@
             KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to R.string.system_multitasking_lhs,
             KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT to R.string.system_multitasking_rhs,
             KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION to R.string.system_multitasking_full_screen,
-            KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT to
-                R.string.system_multitasking_splitscreen_focus_lhs,
-            KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to
-                R.string.system_multitasking_splitscreen_focus_rhs,
         )
 
     val shortcutLabelToKeyGestureTypeMap: Map<String, Int>
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
index df6b04e..d785b5b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
@@ -26,7 +26,6 @@
 import android.view.KeyEvent.KEYCODE_LEFT_BRACKET
 import android.view.KeyEvent.KEYCODE_MINUS
 import android.view.KeyEvent.KEYCODE_RIGHT_BRACKET
-import android.view.KeyEvent.META_ALT_ON
 import android.view.KeyEvent.META_CTRL_ON
 import android.view.KeyEvent.META_META_ON
 import android.view.KeyboardShortcutGroup
@@ -74,20 +73,6 @@
                 command(META_META_ON or META_CTRL_ON, KEYCODE_DPAD_UP)
             }
         )
-        //  Change split screen focus to RHS:
-        //   - Meta + Alt + Right arrow
-        add(
-            shortcutInfo(resources.getString(R.string.system_multitasking_splitscreen_focus_rhs)) {
-                command(META_META_ON or META_ALT_ON, KEYCODE_DPAD_RIGHT)
-            }
-        )
-        //  Change split screen focus to LHS:
-        //   - Meta + Alt + Left arrow
-        add(
-            shortcutInfo(resources.getString(R.string.system_multitasking_splitscreen_focus_lhs)) {
-                command(META_META_ON or META_ALT_ON, KEYCODE_DPAD_LEFT)
-            }
-        )
         if (enableMoveToNextDisplayShortcut()) {
             // Move a window to the next display:
             //  - Meta + Ctrl + D
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 2724918..af6f0cb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -615,7 +615,7 @@
             }
             .focusable(interactionSource = interactionSource)
             .padding(8.dp)
-            .semantics { contentDescription = shortcut.contentDescription }
+            .semantics(mergeDescendants = true) { contentDescription = shortcut.contentDescription }
     ) {
         Row(
             modifier =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 0cb8dd4..5c03d65 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -36,7 +36,6 @@
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapNotNull
 
 /**
@@ -51,17 +50,11 @@
     private val logger: KeyguardTransitionAnimationLogger,
 ) {
     /** Invoke once per transition between FROM->TO states to get access to a shared flow. */
-    fun setup(
-        duration: Duration,
-        edge: Edge,
-    ): FlowBuilder {
+    fun setup(duration: Duration, edge: Edge): FlowBuilder {
         return FlowBuilder(duration, edge)
     }
 
-    inner class FlowBuilder(
-        private val transitionDuration: Duration,
-        private val edge: Edge,
-    ) {
+    inner class FlowBuilder(private val transitionDuration: Duration, private val edge: Edge) {
         fun setupWithoutSceneContainer(edge: Edge.StateToState): FlowBuilder {
             if (SceneContainerFlag.isEnabled) return this
             return setup(this.transitionDuration, edge)
@@ -72,6 +65,8 @@
          * in the range of [0, 1]. View animations should begin and end within a subset of this
          * range. This function maps the [startTime] and [duration] into [0, 1], when this subset is
          * valid.
+         *
+         * Note that [onCancel] isn't used when the scene framework is enabled.
          */
         fun sharedFlow(
             duration: Duration,
@@ -81,7 +76,7 @@
             onCancel: (() -> Float)? = null,
             onFinish: (() -> Float)? = null,
             interpolator: Interpolator = LINEAR,
-            name: String? = null
+            name: String? = null,
         ): Flow<Float> {
             return sharedFlowWithState(
                     duration = duration,
@@ -113,7 +108,7 @@
             onCancel: (() -> Float)? = null,
             onFinish: (() -> Float)? = null,
             interpolator: Interpolator = LINEAR,
-            name: String? = null
+            name: String? = null,
         ): Flow<StateToValue> {
             if (!duration.isPositive()) {
                 throw IllegalArgumentException("duration must be a positive number: $duration")
@@ -155,20 +150,40 @@
 
             return transitionInteractor
                 .transition(edge)
-                .map { step ->
-                    StateToValue(
-                            from = step.from,
-                            to = step.to,
-                            transitionState = step.transitionState,
-                            value =
-                                when (step.transitionState) {
-                                    STARTED -> stepToValue(step)
-                                    RUNNING -> stepToValue(step)
-                                    CANCELED -> onCancel?.invoke()
-                                    FINISHED -> onFinish?.invoke()
-                                }
-                        )
-                        .also { logger.logTransitionStep(name, step, it.value) }
+                .mapNotNull { step ->
+                    if (SceneContainerFlag.isEnabled && step.transitionState == CANCELED) {
+                        // When the scene framework is enabled, there's no need to emit an alpha
+                        // value when the keyguard transition animation is canceled because there's
+                        // always going to be a new, reversed keyguard transition animation back to
+                        // the original KeyguardState that starts right when this one was canceled.
+                        //
+                        // For example, if swiping up slightly on the Lockscreen scene and then
+                        // releasing before the transition to the Bouncer scene is committed, the
+                        // KTF transition of LOCKSCREEN -> PRIMARY_BOUNCER received a CANCELED and
+                        // the scene framework immediately starts a reversed transition of
+                        // PRIMARY_BOUNCER -> LOCKSCREEN, which picks up where the previous one left
+                        // off.
+                        //
+                        // If it were allowed for the CANCELED from the original KTF transition to
+                        // emit a value, a race condition could form where the value from CANCELED
+                        // arrives downstream _after_ the reversed transition is finished, causing
+                        // the transition to end up in an incorrect state at rest.
+                        null
+                    } else {
+                        StateToValue(
+                                from = step.from,
+                                to = step.to,
+                                transitionState = step.transitionState,
+                                value =
+                                    when (step.transitionState) {
+                                        STARTED -> stepToValue(step)
+                                        RUNNING -> stepToValue(step)
+                                        CANCELED -> onCancel?.invoke()
+                                        FINISHED -> onFinish?.invoke()
+                                    },
+                            )
+                            .also { logger.logTransitionStep(name, step, it.value) }
+                    }
                 }
                 .distinctUntilChanged()
         }
@@ -181,7 +196,7 @@
                 duration = 1.milliseconds,
                 onStep = { value },
                 onCancel = { value },
-                onFinish = { value }
+                onFinish = { value },
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 8d9f49e..5b8ac64 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -33,7 +33,6 @@
 import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
 import androidx.annotation.VisibleForTesting
 import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
@@ -246,51 +245,57 @@
     private fun Content() {
         PlatformTheme(isDarkTheme = true) {
             ProvideShortcutHelperIndication(interactionsConfig = interactionsConfig()) {
-                AnimatedVisibility(
-                    visible = viewModel.isQsVisibleAndAnyShadeExpanded,
-                    modifier =
-                        Modifier.graphicsLayer { alpha = viewModel.viewAlpha }
-                            // Clipping before translation to match QSContainerImpl.onDraw
-                            .offset {
-                                IntOffset(x = 0, y = viewModel.viewTranslationY.fastRoundToInt())
-                            }
-                            .thenIf(notificationScrimClippingParams.isEnabled) {
-                                Modifier.notificationScrimClip {
-                                    notificationScrimClippingParams.params
+                if (viewModel.isQsVisibleAndAnyShadeExpanded) {
+                    Box(
+                        modifier =
+                            Modifier.graphicsLayer { alpha = viewModel.viewAlpha }
+                                // Clipping before translation to match QSContainerImpl.onDraw
+                                .offset {
+                                    IntOffset(
+                                        x = 0,
+                                        y = viewModel.viewTranslationY.fastRoundToInt(),
+                                    )
                                 }
+                                .thenIf(notificationScrimClippingParams.isEnabled) {
+                                    Modifier.notificationScrimClip {
+                                        notificationScrimClippingParams.params
+                                    }
+                                }
+                                // Disable touches in the whole composable while the mirror is
+                                // showing. While the mirror is showing, an ancestor of the
+                                // ComposeView is made alpha 0, but touches are still being captured
+                                // by the composables.
+                                .gesturesDisabled(viewModel.showingMirror)
+                    ) {
+                        val isEditing by
+                            viewModel.containerViewModel.editModeViewModel.isEditing
+                                .collectAsStateWithLifecycle()
+                        val animationSpecEditMode = tween<Float>(EDIT_MODE_TIME_MILLIS)
+                        AnimatedContent(
+                            targetState = isEditing,
+                            transitionSpec = {
+                                fadeIn(animationSpecEditMode) togetherWith
+                                    fadeOut(animationSpecEditMode)
+                            },
+                            label = "EditModeAnimatedContent",
+                        ) { editing ->
+                            if (editing) {
+                                val qqsPadding = viewModel.qqsHeaderHeight
+                                EditMode(
+                                    viewModel = viewModel.containerViewModel.editModeViewModel,
+                                    modifier =
+                                        Modifier.fillMaxWidth()
+                                            .padding(top = { qqsPadding })
+                                            .padding(
+                                                horizontal = {
+                                                    QuickSettingsShade.Dimensions.Padding
+                                                        .roundToPx()
+                                                }
+                                            ),
+                                )
+                            } else {
+                                CollapsableQuickSettingsSTL()
                             }
-                            // Disable touches in the whole composable while the mirror is showing.
-                            // While the mirror is showing, an ancestor of the ComposeView is made
-                            // alpha 0, but touches are still being captured by the composables.
-                            .gesturesDisabled(viewModel.showingMirror),
-                ) {
-                    val isEditing by
-                        viewModel.containerViewModel.editModeViewModel.isEditing
-                            .collectAsStateWithLifecycle()
-                    val animationSpecEditMode = tween<Float>(EDIT_MODE_TIME_MILLIS)
-                    AnimatedContent(
-                        targetState = isEditing,
-                        transitionSpec = {
-                            fadeIn(animationSpecEditMode) togetherWith
-                                fadeOut(animationSpecEditMode)
-                        },
-                        label = "EditModeAnimatedContent",
-                    ) { editing ->
-                        if (editing) {
-                            val qqsPadding = viewModel.qqsHeaderHeight
-                            EditMode(
-                                viewModel = viewModel.containerViewModel.editModeViewModel,
-                                modifier =
-                                    Modifier.fillMaxWidth()
-                                        .padding(top = { qqsPadding })
-                                        .padding(
-                                            horizontal = {
-                                                QuickSettingsShade.Dimensions.Padding.roundToPx()
-                                            }
-                                        ),
-                            )
-                        } else {
-                            CollapsableQuickSettingsSTL()
                         }
                     }
                 }
@@ -325,9 +330,15 @@
         }
 
         SceneTransitionLayout(state = sceneState, modifier = Modifier.fillMaxSize()) {
-            scene(QuickSettings) { QuickSettingsElement() }
+            scene(QuickSettings) {
+                LaunchedEffect(Unit) { viewModel.onQSOpen() }
+                QuickSettingsElement()
+            }
 
-            scene(QuickQuickSettings) { QuickQuickSettingsElement() }
+            scene(QuickQuickSettings) {
+                LaunchedEffect(Unit) { viewModel.onQQSOpen() }
+                QuickQuickSettingsElement()
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 3c72520..07ceb64 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -28,6 +28,7 @@
 import androidx.lifecycle.LifecycleCoroutineScope
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.internal.logging.UiEventLogger
 import com.android.keyguard.BouncerPanelExpansionCalculator
 import com.android.systemui.Dumpable
 import com.android.systemui.animation.ShadeInterpolation
@@ -51,6 +52,7 @@
 import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.QSEvent
 import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeLog
 import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeModule
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -113,6 +115,7 @@
     @Named(QUICK_QS_PANEL) val qqsMediaHost: MediaHost,
     @Named(QS_PANEL) val qsMediaHost: MediaHost,
     @Named(QSFragmentComposeModule.QS_USING_MEDIA_PLAYER) private val usingMedia: Boolean,
+    private val uiEventLogger: UiEventLogger,
     @Assisted private val lifecycleScope: LifecycleCoroutineScope,
 ) : Dumpable, ExclusiveActivatable() {
 
@@ -455,6 +458,14 @@
         falsingInteractor.isFalseTouch(Classifier.QS_SWIPE_NESTED)
     }
 
+    fun onQQSOpen() {
+        uiEventLogger.log(QSEvent.QQS_PANEL_EXPANDED)
+    }
+
+    fun onQSOpen() {
+        uiEventLogger.log(QSEvent.QS_PANEL_EXPANDED)
+    }
+
     override suspend fun onActivated(): Nothing {
         initMediaHosts() // init regardless of using media (same as current QS).
         coroutineScope {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
index 35faa97..405ce8a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
@@ -44,19 +44,28 @@
 /** Holds the [TileSpec] of the tile being moved and receives drag and drop events. */
 interface DragAndDropState {
     val draggedCell: SizedTile<EditTileViewModel>?
+    val draggedPosition: Offset
     val dragInProgress: Boolean
+    val dragType: DragType?
 
     fun isMoving(tileSpec: TileSpec): Boolean
 
-    fun onStarted(cell: SizedTile<EditTileViewModel>)
+    fun onStarted(cell: SizedTile<EditTileViewModel>, dragType: DragType)
 
-    fun onMoved(target: Int, insertAfter: Boolean)
+    fun onTargeting(target: Int, insertAfter: Boolean)
+
+    fun onMoved(offset: Offset)
 
     fun movedOutOfBounds()
 
     fun onDrop()
 }
 
+enum class DragType {
+    Add,
+    Move,
+}
+
 /**
  * Registers a composable as a [DragAndDropTarget] to receive drop events. Use this outside the tile
  * grid to catch out of bounds drops.
@@ -72,6 +81,10 @@
     val target =
         remember(dragAndDropState) {
             object : DragAndDropTarget {
+                override fun onMoved(event: DragAndDropEvent) {
+                    dragAndDropState.onMoved(event.toOffset())
+                }
+
                 override fun onDrop(event: DragAndDropEvent): Boolean {
                     return dragAndDropState.draggedCell?.let {
                         onDrop(it.tile.tileSpec)
@@ -117,8 +130,11 @@
                 }
 
                 override fun onMoved(event: DragAndDropEvent) {
+                    val offset = event.toOffset()
+                    dragAndDropState.onMoved(offset)
+
                     // Drag offset relative to the list's top left corner
-                    val relativeDragOffset = event.dragOffsetRelativeTo(contentOffset())
+                    val relativeDragOffset = offset - contentOffset()
                     val targetItem =
                         gridState.layoutInfo.visibleItemsInfo.firstOrNull { item ->
                             // Check if the drag is on this item
@@ -126,7 +142,7 @@
                         }
 
                     targetItem?.let {
-                        dragAndDropState.onMoved(it.index, insertAfter(it, relativeDragOffset))
+                        dragAndDropState.onTargeting(it.index, insertAfter(it, relativeDragOffset))
                     }
                 }
 
@@ -147,8 +163,8 @@
     )
 }
 
-private fun DragAndDropEvent.dragOffsetRelativeTo(offset: Offset): Offset {
-    return toAndroidDragEvent().run { Offset(x, y) } - offset
+private fun DragAndDropEvent.toOffset(): Offset {
+    return toAndroidDragEvent().run { Offset(x, y) }
 }
 
 private fun insertAfter(item: LazyGridItemInfo, offset: Offset): Boolean {
@@ -163,6 +179,7 @@
 fun Modifier.dragAndDropTileSource(
     sizedTile: SizedTile<EditTileViewModel>,
     dragAndDropState: DragAndDropState,
+    dragType: DragType,
     onDragStart: () -> Unit,
 ): Modifier {
     val dragState by rememberUpdatedState(dragAndDropState)
@@ -172,7 +189,7 @@
             detectDragGesturesAfterLongPress(
                 onDrag = { _, _ -> },
                 onDragStart = {
-                    dragState.onStarted(sizedTile)
+                    dragState.onStarted(sizedTile, dragType)
                     onDragStart()
 
                     // The tilespec from the ClipData transferred isn't actually needed as we're
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
index 14abfa2..8688558 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
@@ -17,10 +17,13 @@
 package com.android.systemui.qs.panels.ui.compose
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshots.SnapshotStateList
 import androidx.compose.runtime.toMutableStateList
+import androidx.compose.ui.geometry.Offset
 import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.ui.model.GridCell
 import com.android.systemui.qs.panels.ui.model.TileGridCell
@@ -48,12 +51,17 @@
     private val columns: Int,
     private val largeTilesSpan: Int,
 ) : DragAndDropState {
-    private val _draggedCell = mutableStateOf<SizedTile<EditTileViewModel>?>(null)
-    override val draggedCell
-        get() = _draggedCell.value
+    override var draggedCell by mutableStateOf<SizedTile<EditTileViewModel>?>(null)
+        private set
+
+    override var draggedPosition by mutableStateOf(Offset.Unspecified)
+        private set
+
+    override var dragType by mutableStateOf<DragType?>(null)
+        private set
 
     override val dragInProgress: Boolean
-        get() = _draggedCell.value != null
+        get() = draggedCell != null
 
     private val _tiles: SnapshotStateList<GridCell> =
         tiles.toGridCells(columns).toMutableStateList()
@@ -83,18 +91,19 @@
     }
 
     override fun isMoving(tileSpec: TileSpec): Boolean {
-        return _draggedCell.value?.let { it.tile.tileSpec == tileSpec } ?: false
+        return draggedCell?.let { it.tile.tileSpec == tileSpec } ?: false
     }
 
-    override fun onStarted(cell: SizedTile<EditTileViewModel>) {
-        _draggedCell.value = cell
+    override fun onStarted(cell: SizedTile<EditTileViewModel>, dragType: DragType) {
+        draggedCell = cell
+        this.dragType = dragType
 
         // Add spacers to the grid to indicate where the user can move a tile
         regenerateGrid()
     }
 
-    override fun onMoved(target: Int, insertAfter: Boolean) {
-        val draggedTile = _draggedCell.value ?: return
+    override fun onTargeting(target: Int, insertAfter: Boolean) {
+        val draggedTile = draggedCell ?: return
 
         val fromIndex = indexOf(draggedTile.tile.tileSpec)
         if (fromIndex == target) {
@@ -115,16 +124,26 @@
         regenerateGrid()
     }
 
+    override fun onMoved(offset: Offset) {
+        draggedPosition = offset
+    }
+
     override fun movedOutOfBounds() {
-        val draggedTile = _draggedCell.value ?: return
+        val draggedTile = draggedCell ?: return
 
         _tiles.removeIf { cell ->
             cell is TileGridCell && cell.tile.tileSpec == draggedTile.tile.tileSpec
         }
+        draggedPosition = Offset.Unspecified
+
+        // Regenerate spacers without the dragged tile
+        regenerateGrid()
     }
 
     override fun onDrop() {
-        _draggedCell.value = null
+        draggedCell = null
+        draggedPosition = Offset.Unspecified
+        dragType = null
 
         // Remove the spacers
         regenerateGrid()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index a05747d..d975f10 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -20,12 +20,16 @@
 
 import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.animateContentSize
+import androidx.compose.animation.core.LinearEasing
 import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.LocalOverscrollFactory
+import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.clipScrollableContainer
@@ -43,6 +47,7 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeightIn
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.layout.wrapContentSize
@@ -69,6 +74,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -80,6 +86,7 @@
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.draw.drawBehind
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.isSpecified
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.layout.MeasureScope
@@ -111,6 +118,7 @@
 import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.ui.compose.BounceableInfo
 import com.android.systemui.qs.panels.ui.compose.DragAndDropState
+import com.android.systemui.qs.panels.ui.compose.DragType
 import com.android.systemui.qs.panels.ui.compose.EditTileListState
 import com.android.systemui.qs.panels.ui.compose.bounceableInfo
 import com.android.systemui.qs.panels.ui.compose.dragAndDropRemoveZone
@@ -120,6 +128,9 @@
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileArrangementPadding
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileHeight
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.ToggleTargetSize
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.AUTO_SCROLL_DISTANCE
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.AUTO_SCROLL_SPEED
+import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.AvailableTilesGridMinHeight
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.CurrentTilesGridPadding
 import com.android.systemui.qs.panels.ui.compose.selection.MutableSelectionState
 import com.android.systemui.qs.panels.ui.compose.selection.ResizableTileContainer
@@ -139,8 +150,10 @@
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.shared.model.groupAndSort
 import com.android.systemui.res.R
+import kotlin.math.abs
 import kotlin.math.roundToInt
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
 
 object TileType
@@ -201,8 +214,12 @@
     ) { innerPadding ->
         CompositionLocalProvider(LocalOverscrollFactory provides null) {
             val scrollState = rememberScrollState()
-            LaunchedEffect(listState.dragInProgress) {
-                if (listState.dragInProgress) {
+
+            AutoScrollGrid(listState, scrollState, innerPadding)
+
+            LaunchedEffect(listState.dragType) {
+                // Only scroll to the top when adding a new tile, not when reordering existing ones
+                if (listState.dragInProgress && listState.dragType == DragType.Add) {
                     scrollState.animateScrollTo(0)
                 }
             }
@@ -223,7 +240,7 @@
                 AnimatedContent(
                     targetState = listState.dragInProgress,
                     modifier = Modifier.wrapContentSize(),
-                    label = "",
+                    label = "QSEditHeader",
                 ) { dragIsInProgress ->
                     EditGridHeader(Modifier.dragAndDropRemoveZone(listState, onRemoveTile)) {
                         if (dragIsInProgress) {
@@ -243,37 +260,87 @@
                     onSetTiles,
                 )
 
-                // Hide available tiles when dragging
-                AnimatedVisibility(
-                    visible = !listState.dragInProgress,
-                    enter = fadeIn(),
-                    exit = fadeOut(),
+                // Sets a minimum height to be used when available tiles are hidden
+                Box(
+                    Modifier.fillMaxWidth()
+                        .requiredHeightIn(AvailableTilesGridMinHeight)
+                        .animateContentSize()
+                        .dragAndDropRemoveZone(listState, onRemoveTile)
                 ) {
-                    Column(
-                        verticalArrangement =
-                            spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
-                        modifier = modifier.fillMaxSize(),
+                    // Using the fully qualified name here as a workaround for AnimatedVisibility
+                    // not being available from a Box
+                    androidx.compose.animation.AnimatedVisibility(
+                        visible = !listState.dragInProgress,
+                        enter = fadeIn(),
+                        exit = fadeOut(),
                     ) {
-                        EditGridHeader {
-                            Text(text = stringResource(id = R.string.drag_to_add_tiles))
-                        }
+                        // Hide available tiles when dragging
+                        Column(
+                            verticalArrangement =
+                                spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)),
+                            modifier = modifier.fillMaxSize(),
+                        ) {
+                            EditGridHeader {
+                                Text(text = stringResource(id = R.string.drag_to_add_tiles))
+                            }
 
-                        AvailableTileGrid(otherTiles, selectionState, columns, listState)
+                            AvailableTileGrid(otherTiles, selectionState, columns, listState)
+                        }
                     }
                 }
-
-                // Drop zone to remove tiles dragged out of the tile grid
-                Spacer(
-                    modifier =
-                        Modifier.fillMaxWidth()
-                            .weight(1f)
-                            .dragAndDropRemoveZone(listState, onRemoveTile)
-                )
             }
         }
     }
 }
 
+@OptIn(ExperimentalCoroutinesApi::class)
+@Composable
+private fun AutoScrollGrid(
+    listState: EditTileListState,
+    scrollState: ScrollState,
+    padding: PaddingValues,
+) {
+    val density = LocalDensity.current
+    val (top, bottom) =
+        remember(density) {
+            with(density) {
+                padding.calculateTopPadding().roundToPx() to
+                    padding.calculateBottomPadding().roundToPx()
+            }
+        }
+    val scrollTarget by
+        remember(listState, scrollState, top, bottom) {
+            derivedStateOf {
+                val position = listState.draggedPosition
+                if (position.isSpecified) {
+                    // Return the scroll target needed based on the position of the drag movement,
+                    // or null if we don't need to scroll
+                    val y = position.y.roundToInt()
+                    when {
+                        y < AUTO_SCROLL_DISTANCE + top -> 0
+                        y > scrollState.viewportSize - bottom - AUTO_SCROLL_DISTANCE ->
+                            scrollState.maxValue
+                        else -> null
+                    }
+                } else {
+                    null
+                }
+            }
+        }
+    LaunchedEffect(scrollTarget) {
+        scrollTarget?.let {
+            // Change the duration of the animation based on the distance to maintain the
+            // same scrolling speed
+            val distance = abs(it - scrollState.value)
+            scrollState.animateScrollTo(
+                it,
+                animationSpec =
+                    tween(durationMillis = distance * AUTO_SCROLL_SPEED, easing = LinearEasing),
+            )
+        }
+    }
+}
+
 @Composable
 private fun EditGridHeader(
     modifier: Modifier = Modifier,
@@ -423,7 +490,7 @@
 }
 
 fun gridHeight(rows: Int, tileHeight: Dp, tilePadding: Dp, gridPadding: Dp): Dp {
-    return ((tileHeight + tilePadding) * rows) - tilePadding + gridPadding * 2
+    return ((tileHeight + tilePadding) * rows) + gridPadding * 2
 }
 
 private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any {
@@ -596,6 +663,7 @@
                 .dragAndDropTileSource(
                     SizedTileImpl(cell.tile, cell.width),
                     dragAndDropState,
+                    DragType.Move,
                     selectionState::unSelect,
                 )
                 .tileBackground(colors.background)
@@ -631,7 +699,11 @@
                     onClick(onClickActionName) { false }
                     this.stateDescription = stateDescription
                 }
-                .dragAndDropTileSource(SizedTileImpl(cell.tile, cell.width), dragAndDropState) {
+                .dragAndDropTileSource(
+                    SizedTileImpl(cell.tile, cell.width),
+                    dragAndDropState,
+                    DragType.Add,
+                ) {
                     selectionState.unSelect()
                 }
                 .tileBackground(colors.background)
@@ -739,7 +811,10 @@
 
 private object EditModeTileDefaults {
     const val PLACEHOLDER_ALPHA = .3f
+    const val AUTO_SCROLL_DISTANCE = 100
+    const val AUTO_SCROLL_SPEED = 2 // 2ms per pixel
     val CurrentTilesGridPadding = 8.dp
+    val AvailableTilesGridMinHeight = 200.dp
 
     @Composable
     fun editTileColors(): TileColors =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
index faab696..f7ed1ad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
@@ -18,9 +18,14 @@
 
 import android.content.Context
 import androidx.compose.ui.util.fastMap
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.ListUpdateCallback
+import com.android.internal.logging.UiEventLogger
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.QSEditEvent
 import com.android.systemui.qs.panels.domain.interactor.EditTilesListInteractor
 import com.android.systemui.qs.panels.domain.interactor.GridLayoutTypeInteractor
 import com.android.systemui.qs.panels.domain.interactor.TilesAvailabilityInteractor
@@ -30,10 +35,12 @@
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END
 import com.android.systemui.qs.pipeline.domain.interactor.MinimumTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.metricSpec
 import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.util.kotlin.emitOnStart
 import javax.inject.Inject
 import javax.inject.Named
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -45,6 +52,7 @@
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 @SysUISingleton
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -55,10 +63,12 @@
     private val currentTilesInteractor: CurrentTilesInteractor,
     private val tilesAvailabilityInteractor: TilesAvailabilityInteractor,
     private val minTilesInteractor: MinimumTilesInteractor,
+    private val uiEventLogger: UiEventLogger,
     @ShadeDisplayAware private val configurationInteractor: ConfigurationInteractor,
-    @ShadeDisplayAware  private val context: Context,
+    @ShadeDisplayAware private val context: Context,
     @Named("Default") private val defaultGridLayout: GridLayout,
     @Application private val applicationScope: CoroutineScope,
+    @Background private val bgDispatcher: CoroutineDispatcher,
     gridLayoutTypeInteractor: GridLayoutTypeInteractor,
     gridLayoutMap: Map<GridLayoutType, @JvmSuppressWildcards GridLayout>,
 ) {
@@ -149,11 +159,17 @@
 
     /** @see isEditing */
     fun startEditing() {
+        if (!isEditing.value) {
+            uiEventLogger.log(QSEditEvent.QS_EDIT_OPEN)
+        }
         _isEditing.value = true
     }
 
     /** @see isEditing */
     fun stopEditing() {
+        if (isEditing.value) {
+            uiEventLogger.log(QSEditEvent.QS_EDIT_CLOSED)
+        }
         _isEditing.value = false
     }
 
@@ -164,6 +180,7 @@
     fun addTile(tileSpec: TileSpec, position: Int = POSITION_AT_END) {
         val specs = currentTilesInteractor.currentTilesSpecs.toMutableList()
         val currentPosition = specs.indexOf(tileSpec)
+        val moved = currentPosition != -1
 
         if (currentPosition != -1) {
             // No operation needed if the element is already in the list at the right position
@@ -179,6 +196,12 @@
         } else {
             specs.add(tileSpec)
         }
+        uiEventLogger.logWithPosition(
+            if (moved) QSEditEvent.QS_EDIT_MOVE else QSEditEvent.QS_EDIT_ADD,
+            /* uid= */ 0,
+            /* packageName= */ tileSpec.metricSpec,
+            if (moved && position == POSITION_AT_END) specs.size - 1 else position,
+        )
 
         // Setting the new tiles as one operation to avoid UI jank with tiles disappearing and
         // reappearing
@@ -187,10 +210,80 @@
 
     /** Immediately removes [tileSpec] from the current tiles. */
     fun removeTile(tileSpec: TileSpec) {
+        uiEventLogger.log(
+            QSEditEvent.QS_EDIT_REMOVE,
+            /* uid= */ 0,
+            /* packageName= */ tileSpec.metricSpec,
+        )
         currentTilesInteractor.removeTiles(listOf(tileSpec))
     }
 
     fun setTiles(tileSpecs: List<TileSpec>) {
+        val currentTiles = currentTilesInteractor.currentTilesSpecs
         currentTilesInteractor.setTiles(tileSpecs)
+        applicationScope.launch(bgDispatcher) {
+            calculateDiffsAndEmitUiEvents(currentTiles, tileSpecs)
+        }
+    }
+
+    private fun calculateDiffsAndEmitUiEvents(
+        currentTiles: List<TileSpec>,
+        newTiles: List<TileSpec>,
+    ) {
+        val listDiff = DiffUtil.calculateDiff(DiffCallback(currentTiles, newTiles))
+        listDiff.dispatchUpdatesTo(
+            object : ListUpdateCallback {
+                override fun onInserted(position: Int, count: Int) {
+                    newTiles.getOrNull(position)?.let {
+                        uiEventLogger.logWithPosition(
+                            QSEditEvent.QS_EDIT_ADD,
+                            /* uid= */ 0,
+                            /* packageName= */ it.metricSpec,
+                            position,
+                        )
+                    }
+                }
+
+                override fun onRemoved(position: Int, count: Int) {
+                    currentTiles.getOrNull(position)?.let {
+                        uiEventLogger.log(QSEditEvent.QS_EDIT_REMOVE, 0, it.metricSpec)
+                    }
+                }
+
+                override fun onMoved(fromPosition: Int, toPosition: Int) {
+                    currentTiles.getOrNull(fromPosition)?.let {
+                        uiEventLogger.logWithPosition(
+                            QSEditEvent.QS_EDIT_MOVE,
+                            /* uid= */ 0,
+                            /* packageName= */ it.metricSpec,
+                            toPosition,
+                        )
+                    }
+                }
+
+                override fun onChanged(position: Int, count: Int, payload: Any?) {}
+            }
+        )
+    }
+}
+
+private class DiffCallback(
+    private val currentList: List<TileSpec>,
+    private val newList: List<TileSpec>,
+) : DiffUtil.Callback() {
+    override fun getOldListSize(): Int {
+        return currentList.size
+    }
+
+    override fun getNewListSize(): Int {
+        return newList.size
+    }
+
+    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+        return currentList[oldItemPosition] == newList[newItemPosition]
+    }
+
+    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
+        return areItemsTheSame(oldItemPosition, newItemPosition)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
index 2e52845..16c2722 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
@@ -34,10 +34,7 @@
     data object Invalid : TileSpec("")
 
     /** Container for the spec of a tile provided by SystemUI. */
-    data class PlatformTileSpec
-    internal constructor(
-        override val spec: String,
-    ) : TileSpec(spec) {
+    data class PlatformTileSpec internal constructor(override val spec: String) : TileSpec(spec) {
         override fun toString(): String {
             return "P($spec)"
         }
@@ -49,10 +46,8 @@
      * [componentName] indicates the associated `TileService`.
      */
     data class CustomTileSpec
-    internal constructor(
-        override val spec: String,
-        val componentName: ComponentName,
-    ) : TileSpec(spec) {
+    internal constructor(override val spec: String, val componentName: ComponentName) :
+        TileSpec(spec) {
         override fun toString(): String {
             return "C(${componentName.flattenToShortString()})"
         }
@@ -92,3 +87,11 @@
                 }
     }
 }
+
+val TileSpec.metricSpec
+    get() =
+        when (this) {
+            is TileSpec.Invalid -> ""
+            is TileSpec.PlatformTileSpec -> spec
+            is TileSpec.CustomTileSpec -> componentName.packageName
+        }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
index 30b68927..c241f21 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java
@@ -62,7 +62,7 @@
         } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
             setHovered(false);
         }
-        return true;
+        return super.onHoverEvent(event);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index f5fc1f4..bf672be 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -166,6 +166,15 @@
     }
 
     @Override
+    public void onMovedToDisplay(int displayId, Configuration config) {
+        super.onMovedToDisplay(displayId, config);
+        ShadeWindowGoesAround.isUnexpectedlyInLegacyMode();
+        // When the window is moved we're only receiving a call to this method instead of the
+        // onConfigurationChange itself. Let's just trigegr a normal config change.
+        onConfigurationChanged(config);
+    }
+
+    @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         if (mConfigurationForwarder != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index 2bd7393..0954e5e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -22,6 +22,7 @@
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams
 import android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE
+import android.window.WindowContext
 import com.android.systemui.CoreStartable
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.common.ui.ConfigurationStateImpl
@@ -82,6 +83,19 @@
     @Provides
     @ShadeDisplayAware
     @SysUISingleton
+    fun provideShadeDisplayAwareWindowContext(@ShadeDisplayAware context: Context): WindowContext {
+        ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
+        // We rely on the fact context is a WindowContext as the API to reparent windows is only
+        // available there.
+        return (context as? WindowContext)
+            ?: error(
+                "ShadeDisplayAware context must be a window context to allow window reparenting."
+            )
+    }
+
+    @Provides
+    @ShadeDisplayAware
+    @SysUISingleton
     fun provideShadeWindowLayoutParams(@ShadeDisplayAware context: Context): LayoutParams {
         return ShadeWindowLayoutParams.create(context)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
index 3414867..08c03e2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
@@ -16,10 +16,8 @@
 
 package com.android.systemui.shade.domain.interactor
 
-import android.content.Context
 import android.util.Log
-import android.view.WindowManager
-import android.view.WindowManager.LayoutParams
+import android.window.WindowContext
 import androidx.annotation.UiThread
 import com.android.app.tracing.coroutines.launchTraced
 import com.android.app.tracing.traceSection
@@ -45,9 +43,7 @@
 constructor(
     optionalShadeRootView: Optional<WindowRootView>,
     private val shadePositionRepository: ShadeDisplaysRepository,
-    @ShadeDisplayAware private val shadeContext: Context,
-    @ShadeDisplayAware private val shadeLayoutParams: LayoutParams,
-    @ShadeDisplayAware private val wm: WindowManager,
+    @ShadeDisplayAware private val shadeContext: WindowContext,
     @Background private val bgScope: CoroutineScope,
     @Main private val mainThreadContext: CoroutineContext,
 ) : CoreStartable {
@@ -72,7 +68,11 @@
     /** Tries to move the shade. If anything wrong happens, fails gracefully without crashing. */
     private suspend fun moveShadeWindowTo(destinationId: Int) {
         Log.d(TAG, "Trying to move shade window to display with id $destinationId")
-        val currentDisplay = shadeRootView.display
+        // Why using the shade context here instead of the view's Display?
+        // The context's display is updated before the view one, so it is a better indicator of
+        // which display the shade is supposed to be at. The View display is updated after the first
+        // rendering with the new config.
+        val currentDisplay = shadeContext.display
         if (currentDisplay == null) {
             Log.w(TAG, "Current shade display is null")
             return
@@ -83,7 +83,7 @@
             return
         }
         try {
-            withContext(mainThreadContext) { moveShadeWindow(toId = destinationId) }
+            withContext(mainThreadContext) { reparentToDisplayId(id = destinationId) }
         } catch (e: IllegalStateException) {
             Log.e(
                 TAG,
@@ -94,25 +94,8 @@
     }
 
     @UiThread
-    private fun moveShadeWindow(toId: Int) {
-        traceSection({ "moveShadeWindow  to $toId" }) {
-            removeShadeWindow()
-            updateContextDisplay(toId)
-            addShadeWindow()
-        }
-    }
-
-    @UiThread
-    private fun removeShadeWindow(): Unit =
-        traceSection("removeShadeWindow") { wm.removeView(shadeRootView) }
-
-    @UiThread
-    private fun addShadeWindow(): Unit =
-        traceSection("addShadeWindow") { wm.addView(shadeRootView, shadeLayoutParams) }
-
-    @UiThread
-    private fun updateContextDisplay(newDisplayId: Int) {
-        traceSection("updateContextDisplay") { shadeContext.updateDisplay(newDisplayId) }
+    private fun reparentToDisplayId(id: Int) {
+        traceSection({ "reparentToDisplayId(id=$id)" }) { shadeContext.reparentToDisplay(id) }
     }
 
     private companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index 6dbb714..643ee24 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -16,7 +16,6 @@
 package com.android.systemui.statusbar.notification.icon.ui.viewbinder
 
 import android.graphics.Color
-import android.graphics.Rect
 import android.util.Log
 import android.view.View
 import android.view.ViewGroup
@@ -53,7 +52,6 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.stateIn
 
 /** Binds a view-model to a [NotificationIconContainer]. */
@@ -71,10 +69,7 @@
         launch {
             val contrastColorUtil = ContrastColorUtil.getInstance(view.context)
             val iconColors: StateFlow<NotificationIconColors> =
-                viewModel
-                    .iconColors(displayId)
-                    .mapNotNull { it.iconColors(view.viewBounds) }
-                    .stateIn(this)
+                viewModel.iconColors(displayId).stateIn(this)
             viewModel.icons.bindIcons(
                 logTag = "statusbar",
                 view = view,
@@ -374,18 +369,6 @@
         getEntry(key)?.icons?.let(block)
     }
 
-private val View.viewBounds: Rect
-    get() {
-        val tmpArray = intArrayOf(0, 0)
-        getLocationOnScreen(tmpArray)
-        return Rect(
-            /* left = */ tmpArray[0],
-            /* top = */ tmpArray[1],
-            /* right = */ left + width,
-            /* bottom = */ top + height,
-        )
-    }
-
 private suspend inline fun <T> Flow<T>.collectTracingEach(
     tag: String,
     crossinline collector: (T) -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
index 83f56a0..124bd2ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.notification.icon.ui.viewbinder
 
-import android.graphics.Rect
-import android.view.View
 import com.android.app.tracing.traceSection
 import com.android.internal.util.ContrastColorUtil
 import com.android.systemui.res.R
@@ -25,6 +23,7 @@
 import com.android.systemui.statusbar.StatusBarIconView.NO_COLOR
 import com.android.systemui.statusbar.notification.NotificationUtils
 import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconColors
+import com.android.systemui.util.view.viewBoundsOnScreen
 import kotlinx.coroutines.flow.Flow
 
 object StatusBarIconViewBinder {
@@ -60,25 +59,13 @@
             val isPreL = java.lang.Boolean.TRUE == view.getTag(R.id.icon_is_pre_L)
             val isColorized = !isPreL || NotificationUtils.isGrayscale(view, contrastColorUtil)
             view.staticDrawableColor =
-                if (isColorized) colors.staticDrawableColor(view.viewBounds) else NO_COLOR
+                if (isColorized) colors.staticDrawableColor(view.viewBoundsOnScreen()) else NO_COLOR
             // Set the color for the overflow dot
             view.setDecorColor(colors.tint)
         }
     }
 }
 
-private val View.viewBounds: Rect
-    get() {
-        val tmpArray = intArrayOf(0, 0)
-        getLocationOnScreen(tmpArray)
-        return Rect(
-            /* left = */ tmpArray[0],
-            /* top = */ tmpArray[1],
-            /* right = */ left + width,
-            /* bottom = */ top + height,
-        )
-    }
-
 private suspend inline fun <T> Flow<T>.collectTracingEach(
     tag: String,
     crossinline collector: (T) -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt
index 2365db4..a9635dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconColors.kt
@@ -17,14 +17,6 @@
 
 import android.graphics.Rect
 
-/**
- * Lookup the colors to use for the notification icons based on the bounds of the icon container. A
- * result of `null` indicates that no color changes should be applied.
- */
-fun interface NotificationIconColorLookup {
-    fun iconColors(viewBounds: Rect): NotificationIconColors?
-}
-
 /** Colors to apply to notification icons. */
 interface NotificationIconColors {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index f0b0306..2ba28a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -68,18 +68,10 @@
             .distinctUntilChanged()
 
     /** The colors with which to display the notification icons. */
-    fun iconColors(displayId: Int): Flow<NotificationIconColorLookup> =
+    fun iconColors(displayId: Int): Flow<NotificationIconColors> =
         darkIconInteractor
             .darkState(displayId)
-            .map { (areas: Collection<Rect>, tint: Int) ->
-                NotificationIconColorLookup { viewBounds: Rect ->
-                    if (DarkIconDispatcher.isInAreas(areas, viewBounds)) {
-                        IconColorsImpl(tint, areas)
-                    } else {
-                        null
-                    }
-                }
-            }
+            .map { (areas: Collection<Rect>, tint: Int) -> IconColorsImpl(tint, areas) }
             .flowOn(bgContext)
             .conflate()
             .distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index f8aff69..9d13ab5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -21,7 +21,6 @@
 import android.graphics.drawable.AnimatedImageDrawable
 import android.view.View
 import android.view.ViewGroup
-import android.view.ViewGroup.MarginLayoutParams
 import com.android.internal.widget.CachingIconView
 import com.android.internal.widget.ConversationLayout
 import com.android.internal.widget.MessagingGroup
@@ -94,13 +93,6 @@
         // Reinspect the notification. Before the super call, because the super call also updates
         // the transformation types and we need to have our values set by then.
         resolveViews()
-        if (Flags.notificationsRedesignAppIcons() && row.isShowingAppIcon) {
-            // Override the margins to be 2dp instead of 4dp according to the new design if we're
-            // showing the app icon.
-            val lp = badgeIconView.layoutParams as MarginLayoutParams
-            lp.setMargins(2, 2, 2, 2)
-            badgeIconView.layoutParams = lp
-        }
         super.onContentUpdated(row)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index bffcae9..b456168 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -147,8 +147,7 @@
             // The footer needs to be re-inflated every time the theme or the font size changes.
             configuration
                 .inflateLayout<FooterView>(
-                    if (NotifRedesignFooter.isEnabled)
-                        R.layout.status_bar_notification_footer_redesign
+                    if (NotifRedesignFooter.isEnabled) R.layout.notification_2025_footer
                     else R.layout.status_bar_notification_footer,
                     parentView,
                     attachToRoot = false,
diff --git a/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt b/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt
index 6160b00..5b48c1f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt
@@ -35,27 +35,21 @@
     fun touchIsWithinView(view: View, x: Float, y: Float): Boolean {
         val left = view.locationOnScreen[0]
         val top = view.locationOnScreen[1]
-        return left <= x &&
-                x <= left + view.width &&
-                top <= y &&
-                y <= top + view.height
+        return left <= x && x <= left + view.width && top <= y && y <= top + view.height
     }
 
-    /**
-     * Sets [outRect] to be the view's location within its window.
-     */
-    fun setRectToViewWindowLocation(view: View, outRect: Rect) {
-        val locInWindow = IntArray(2)
-        view.getLocationInWindow(locInWindow)
-
-        val x = locInWindow[0]
-        val y = locInWindow[1]
-
-        outRect.set(
-            x,
-            y,
-            x + view.width,
-            y + view.height,
-        )
-    }
+    /** Sets [outRect] to be the view's location within its window. */
+    fun setRectToViewWindowLocation(view: View, outRect: Rect) = view.viewBoundsOnScreen(outRect)
 }
+
+fun View.viewBoundsOnScreen(outRect: Rect) {
+    val locInWindow = IntArray(2)
+    getLocationInWindow(locInWindow)
+
+    val x = locInWindow[0]
+    val y = locInWindow[1]
+
+    outRect.set(x, y, x + width, y + height)
+}
+
+fun View.viewBoundsOnScreen(): Rect = Rect().also { viewBoundsOnScreen(it) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
index d2317e4..fc720b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
@@ -87,7 +87,7 @@
         }
         composeRule.waitForIdle()
 
-        listState.onStarted(TestEditTiles[0])
+        listState.onStarted(TestEditTiles[0], DragType.Add)
 
         // Tile is being dragged, it should be replaced with a placeholder
         composeRule.onNodeWithContentDescription("tileA").assertDoesNotExist()
@@ -113,8 +113,8 @@
         }
         composeRule.waitForIdle()
 
-        listState.onStarted(TestEditTiles[0])
-        listState.onMoved(1, false)
+        listState.onStarted(TestEditTiles[0], DragType.Add)
+        listState.onTargeting(1, false)
         listState.onDrop()
 
         // Available tiles should re-appear
@@ -140,7 +140,7 @@
         }
         composeRule.waitForIdle()
 
-        listState.onStarted(TestEditTiles[0])
+        listState.onStarted(TestEditTiles[0], DragType.Add)
         listState.movedOutOfBounds()
         listState.onDrop()
 
@@ -165,11 +165,11 @@
         }
         composeRule.waitForIdle()
 
-        listState.onStarted(createEditTile("newTile"))
+        listState.onStarted(createEditTile("newTile"), DragType.Add)
         // Insert after tileD, which is at index 4
         // [ a ] [ b ] [ c ] [ empty ]
         // [ tile d ] [ e ]
-        listState.onMoved(4, insertAfter = true)
+        listState.onTargeting(4, insertAfter = true)
         listState.onDrop()
 
         // Available tiles should re-appear
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
index d71bc31..49957f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -18,6 +18,7 @@
 
 import android.content.res.mainResources
 import androidx.lifecycle.LifecycleCoroutineScope
+import com.android.internal.logging.uiEventLoggerFake
 import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
@@ -68,6 +69,7 @@
                     qqsMediaHost,
                     qsMediaHost,
                     usingMediaInComposeFragment,
+                    uiEventLoggerFake,
                     lifecycleScope,
                 )
             }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt
index 86c3add..71408f6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt
@@ -17,9 +17,11 @@
 package com.android.systemui.qs.panels.ui.viewmodel
 
 import android.content.applicationContext
+import com.android.internal.logging.uiEventLoggerFake
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.qs.panels.domain.interactor.editTilesListInteractor
 import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap
 import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor
@@ -35,10 +37,12 @@
             currentTilesInteractor,
             tilesAvailabilityInteractor,
             minimumTilesInteractor,
+            uiEventLoggerFake,
             configurationInteractor,
             applicationContext,
             infiniteGridLayout,
             applicationCoroutineScope,
+            testDispatcher,
             gridLayoutTypeInteractor,
             gridLayoutMap,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
index db4df38..f2af619 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
@@ -17,25 +17,24 @@
 package com.android.systemui.shade.domain.interactor
 
 import android.content.mockedContext
-import android.view.mockWindowManager
+import android.window.WindowContext
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.ui.view.mockShadeRootView
 import com.android.systemui.shade.ShadeWindowLayoutParams
 import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
 import java.util.Optional
+import org.mockito.kotlin.mock
 
-val Kosmos.shadeLayoutParams by Kosmos.Fixture {
-    ShadeWindowLayoutParams.create(mockedContext)
-}
+val Kosmos.shadeLayoutParams by Kosmos.Fixture { ShadeWindowLayoutParams.create(mockedContext) }
+
+val Kosmos.mockedWindowContext by Kosmos.Fixture { mock<WindowContext>() }
 val Kosmos.shadeDisplaysInteractor by
     Kosmos.Fixture {
         ShadeDisplaysInteractor(
             Optional.of(mockShadeRootView),
             fakeShadeDisplaysRepository,
-            mockedContext,
-            shadeLayoutParams,
-            mockWindowManager,
+            mockedWindowContext,
             testScope.backgroundScope,
             testScope.backgroundScope.coroutineContext,
         )
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt
index 8bf3a43..ae9b8c8 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/Combinators.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.kairos
 
 import com.android.systemui.kairos.util.These
+import com.android.systemui.kairos.util.WithPrev
 import com.android.systemui.kairos.util.just
 import com.android.systemui.kairos.util.none
 import kotlinx.coroutines.flow.Flow
@@ -27,15 +28,18 @@
  * Returns a [TFlow] that emits the value sampled from the [Transactional] produced by each emission
  * of the original [TFlow], within the same transaction of the original emission.
  */
+@ExperimentalFrpApi
 fun <A> TFlow<Transactional<A>>.sampleTransactionals(): TFlow<A> = map { it.sample() }
 
 /** @see FrpTransactionScope.sample */
+@ExperimentalFrpApi
 fun <A, B, C> TFlow<A>.sample(
     state: TState<B>,
     transform: suspend FrpTransactionScope.(A, B) -> C,
 ): TFlow<C> = map { transform(it, state.sample()) }
 
 /** @see FrpTransactionScope.sample */
+@ExperimentalFrpApi
 fun <A, B, C> TFlow<A>.sample(
     transactional: Transactional<B>,
     transform: suspend FrpTransactionScope.(A, B) -> C,
@@ -50,6 +54,7 @@
  *
  * @see sample
  */
+@ExperimentalFrpApi
 fun <A, B, C> TFlow<A>.samplePromptly(
     state: TState<B>,
     transform: suspend FrpTransactionScope.(A, B) -> C,
@@ -70,19 +75,10 @@
         }
 
 /**
- * Returns a [TState] containing a map with a snapshot of the current state of each [TState] in the
- * original map.
- */
-fun <K, A> Map<K, TState<A>>.combineValues(): TState<Map<K, A>> =
-    asIterable()
-        .map { (k, state) -> state.map { v -> k to v } }
-        .combine()
-        .map { entries -> entries.toMap() }
-
-/**
  * Returns a cold [Flow] that, when collected, emits from this [TFlow]. [network] is needed to
  * transactionally connect to / disconnect from the [TFlow] when collection starts/stops.
  */
+@ExperimentalFrpApi
 fun <A> TFlow<A>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
     channelFlow { network.activateSpec { observe { trySend(it) } } }.conflate()
 
@@ -90,6 +86,7 @@
  * Returns a cold [Flow] that, when collected, emits from this [TState]. [network] is needed to
  * transactionally connect to / disconnect from the [TState] when collection starts/stops.
  */
+@ExperimentalFrpApi
 fun <A> TState<A>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
     channelFlow { network.activateSpec { observe { trySend(it) } } }.conflate()
 
@@ -99,6 +96,7 @@
  *
  * When collection is cancelled, so is the [FrpSpec]. This means all ongoing work is cleaned up.
  */
+@ExperimentalFrpApi
 @JvmName("flowSpecToColdConflatedFlow")
 fun <A> FrpSpec<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
     channelFlow { network.activateSpec { applySpec().observe { trySend(it) } } }.conflate()
@@ -109,6 +107,7 @@
  *
  * When collection is cancelled, so is the [FrpSpec]. This means all ongoing work is cleaned up.
  */
+@ExperimentalFrpApi
 @JvmName("stateSpecToColdConflatedFlow")
 fun <A> FrpSpec<TState<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
     channelFlow { network.activateSpec { applySpec().observe { trySend(it) } } }.conflate()
@@ -117,6 +116,7 @@
  * Returns a cold [Flow] that, when collected, applies this [Transactional] in a new transaction in
  * this [network], and then emits from the returned [TFlow].
  */
+@ExperimentalFrpApi
 @JvmName("transactionalFlowToColdConflatedFlow")
 fun <A> Transactional<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
     channelFlow { network.activateSpec { sample().observe { trySend(it) } } }.conflate()
@@ -125,6 +125,7 @@
  * Returns a cold [Flow] that, when collected, applies this [Transactional] in a new transaction in
  * this [network], and then emits from the returned [TState].
  */
+@ExperimentalFrpApi
 @JvmName("transactionalStateToColdConflatedFlow")
 fun <A> Transactional<TState<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
     channelFlow { network.activateSpec { sample().observe { trySend(it) } } }.conflate()
@@ -135,6 +136,7 @@
  *
  * When collection is cancelled, so is the [FrpStateful]. This means all ongoing work is cleaned up.
  */
+@ExperimentalFrpApi
 @JvmName("statefulFlowToColdConflatedFlow")
 fun <A> FrpStateful<TFlow<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
     channelFlow { network.activateSpec { applyStateful().observe { trySend(it) } } }.conflate()
@@ -145,11 +147,13 @@
  *
  * When collection is cancelled, so is the [FrpStateful]. This means all ongoing work is cleaned up.
  */
+@ExperimentalFrpApi
 @JvmName("statefulStateToColdConflatedFlow")
 fun <A> FrpStateful<TState<A>>.toColdConflatedFlow(network: FrpNetwork): Flow<A> =
     channelFlow { network.activateSpec { applyStateful().observe { trySend(it) } } }.conflate()
 
 /** Return a [TFlow] that emits from the original [TFlow] only when [state] is `true`. */
+@ExperimentalFrpApi
 fun <A> TFlow<A>.filter(state: TState<Boolean>): TFlow<A> = filter { state.sample() }
 
 private fun Iterable<Boolean>.allTrue() = all { it }
@@ -157,13 +161,15 @@
 private fun Iterable<Boolean>.anyTrue() = any { it }
 
 /** Returns a [TState] that is `true` only when all of [states] are `true`. */
+@ExperimentalFrpApi
 fun allOf(vararg states: TState<Boolean>): TState<Boolean> = combine(*states) { it.allTrue() }
 
 /** Returns a [TState] that is `true` when any of [states] are `true`. */
+@ExperimentalFrpApi
 fun anyOf(vararg states: TState<Boolean>): TState<Boolean> = combine(*states) { it.anyTrue() }
 
 /** Returns a [TState] containing the inverse of the Boolean held by the original [TState]. */
-fun not(state: TState<Boolean>): TState<Boolean> = state.mapCheapUnsafe { !it }
+@ExperimentalFrpApi fun not(state: TState<Boolean>): TState<Boolean> = state.mapCheapUnsafe { !it }
 
 /**
  * Represents a modal FRP sub-network.
@@ -177,6 +183,7 @@
  *
  * @see FrpStatefulMode
  */
+@ExperimentalFrpApi
 fun interface FrpBuildMode<out A> {
     /**
      * Invoked when this mode is enabled. Returns a value and a [TFlow] that signals a switch to a
@@ -192,6 +199,7 @@
  *
  * @see FrpBuildMode
  */
+@ExperimentalFrpApi
 val <A> FrpBuildMode<A>.compiledFrpSpec: FrpSpec<TState<A>>
     get() = frpSpec {
         var modeChangeEvents by TFlowLoop<FrpBuildMode<A>>()
@@ -215,6 +223,7 @@
  *
  * @see FrpBuildMode
  */
+@ExperimentalFrpApi
 fun interface FrpStatefulMode<out A> {
     /**
      * Invoked when this mode is enabled. Returns a value and a [TFlow] that signals a switch to a
@@ -230,6 +239,7 @@
  *
  * @see FrpBuildMode
  */
+@ExperimentalFrpApi
 val <A> FrpStatefulMode<A>.compiledStateful: FrpStateful<TState<A>>
     get() = statefully {
         var modeChangeEvents by TFlowLoop<FrpStatefulMode<A>>()
@@ -246,5 +256,18 @@
  * Runs [spec] in this [FrpBuildScope], and then re-runs it whenever [rebuildSignal] emits. Returns
  * a [TState] that holds the result of the currently-active [FrpSpec].
  */
+@ExperimentalFrpApi
 fun <A> FrpBuildScope.rebuildOn(rebuildSignal: TFlow<*>, spec: FrpSpec<A>): TState<A> =
     rebuildSignal.map { spec }.holdLatestSpec(spec)
+
+/**
+ * Like [stateChanges] but also includes the old value of this [TState].
+ *
+ * Shorthand for:
+ * ``` kotlin
+ *     stateChanges.map { WithPrev(previousValue = sample(), newValue = it) }
+ * ```
+ */
+@ExperimentalFrpApi
+val <A> TState<A>.transitions: TFlow<WithPrev<A, A>>
+    get() = stateChanges.map { WithPrev(previousValue = sample(), newValue = it) }
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpBuildScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpBuildScope.kt
index 4de6deb..209a402 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpBuildScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpBuildScope.kt
@@ -38,6 +38,7 @@
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.dropWhile
+import kotlinx.coroutines.flow.scan
 import kotlinx.coroutines.launch
 
 /** A function that modifies the FrpNetwork. */
@@ -596,6 +597,26 @@
     fun <A> Flow<A>.toTState(initialValue: A): TState<A> = toTFlow().hold(initialValue)
 
     /**
+     * Shorthand for:
+     * ```kotlin
+     * flow.scan(initialValue, operation).toTFlow().hold(initialValue)
+     * ```
+     */
+    @ExperimentalFrpApi
+    fun <A, B> Flow<A>.scanToTState(initialValue: B, operation: (B, A) -> B): TState<B> =
+        scan(initialValue, operation).toTFlow().hold(initialValue)
+
+    /**
+     * Shorthand for:
+     * ```kotlin
+     * flow.scan(initialValue) { a, f -> f(a) }.toTFlow().hold(initialValue)
+     * ```
+     */
+    @ExperimentalFrpApi
+    fun <A> Flow<(A) -> A>.scanToTState(initialValue: A): TState<A> =
+        scanToTState(initialValue) { a, f -> f(a) }
+
+    /**
      * Invokes [block] whenever this [TFlow] emits a value. [block] receives an [FrpBuildScope] that
      * can be used to make further modifications to the FRP network, and/or perform side-effects via
      * [effect].
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt
index be2eb43..b39dcc1 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpEffectScope.kt
@@ -22,16 +22,17 @@
 /**
  * Scope for external side-effects triggered by the Frp network. This still occurs within the
  * context of a transaction, so general suspending calls are disallowed to prevent blocking the
- * transaction. You can use [frpCoroutineScope] to [launch] new coroutines to perform long-running
- * asynchronous work. This scope is alive for the duration of the containing [FrpBuildScope] that
- * this side-effect scope is running in.
+ * transaction. You can use [frpCoroutineScope] to [launch][kotlinx.coroutines.launch] new
+ * coroutines to perform long-running asynchronous work. This scope is alive for the duration of the
+ * containing [FrpBuildScope] that this side-effect scope is running in.
  */
 @RestrictsSuspension
 @ExperimentalFrpApi
 interface FrpEffectScope : FrpTransactionScope {
     /**
      * A [CoroutineScope] whose lifecycle lives for as long as this [FrpEffectScope] is alive. This
-     * is generally until the [Job] returned by [FrpBuildScope.effect] is cancelled.
+     * is generally until the [Job][kotlinx.coroutines.Job] returned by [FrpBuildScope.effect] is
+     * cancelled.
      */
     @ExperimentalFrpApi val frpCoroutineScope: CoroutineScope
 
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt
index b688eaf..97252b4 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt
@@ -137,7 +137,7 @@
     override suspend fun <R> transact(block: suspend FrpTransactionScope.() -> R): R {
         val result = CompletableDeferred<R>(coroutineContext[Job])
         @Suppress("DeferredResultUnused")
-        network.transaction {
+        network.transaction("FrpNetwork.transact") {
             val buildScope =
                 BuildScopeImpl(
                     stateScope = StateScopeImpl(evalScope = this, endSignal = endSignal),
@@ -151,7 +151,7 @@
     override suspend fun activateSpec(spec: FrpSpec<*>) {
         val job =
             network
-                .transaction {
+                .transaction("FrpNetwork.activateSpec") {
                     val buildScope =
                         BuildScopeImpl(
                             stateScope = StateScopeImpl(evalScope = this, endSignal = endSignal),
@@ -166,7 +166,8 @@
     override fun <In, Out> coalescingMutableTFlow(
         coalesce: (old: Out, new: In) -> Out,
         getInitialValue: () -> Out,
-    ): CoalescingMutableTFlow<In, Out> = CoalescingMutableTFlow(coalesce, network, getInitialValue)
+    ): CoalescingMutableTFlow<In, Out> =
+        CoalescingMutableTFlow(null, coalesce, network, getInitialValue)
 
     override fun <T> mutableTFlow(): MutableTFlow<T> = MutableTFlow(network)
 
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt
index 7ba1aca..a175e2e 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TFlow.kt
@@ -467,12 +467,12 @@
 @ExperimentalFrpApi
 class CoalescingMutableTFlow<In, Out>
 internal constructor(
+    internal val name: String?,
     internal val coalesce: (old: Out, new: In) -> Out,
     internal val network: Network,
     private val getInitialValue: () -> Out,
     internal val impl: InputNode<Out> = InputNode(),
 ) : TFlow<Out>() {
-    internal val name: String? = null
     internal val storage = AtomicReference(false to getInitialValue())
 
     override fun toString(): String = "${this::class.simpleName}@$hashString"
@@ -490,7 +490,7 @@
         val (scheduled, _) = storage.getAndUpdate { (_, old) -> true to coalesce(old, value) }
         if (!scheduled) {
             @Suppress("DeferredResultUnused")
-            network.transaction {
+            network.transaction("CoalescingMutableTFlow${name?.let { "($name)" }.orEmpty()}.emit") {
                 impl.visit(this, storage.getAndSet(false to getInitialValue()).second)
             }
         }
@@ -520,16 +520,16 @@
     @ExperimentalFrpApi
     suspend fun emit(value: T) {
         coroutineScope {
+            var jobOrNull: Job? = null
             val newEmit =
                 async(start = CoroutineStart.LAZY) {
-                    network.transaction { impl.visit(this, value) }.await()
+                    jobOrNull?.join()
+                    network
+                        .transaction("MutableTFlow($name).emit") { impl.visit(this, value) }
+                        .await()
                 }
-            val jobOrNull = storage.getAndSet(newEmit)
-            if (jobOrNull?.isActive != true) {
-                newEmit.await()
-            } else {
-                jobOrNull.join()
-            }
+            jobOrNull = storage.getAndSet(newEmit)
+            newEmit.await()
         }
     }
 
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt
index a4c6956..80e7474 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/TState.kt
@@ -121,7 +121,7 @@
 
 /**
  * Splits a [TState] of pairs into a pair of [TFlows][TState], where each returned [TState] holds
- * hald of the original.
+ * half of the original.
  *
  * Shorthand for:
  * ```kotlin
@@ -312,6 +312,57 @@
     )
 }
 
+/**
+ * Returns a [TState] whose value is generated with [transform] by combining the current values of
+ * each given [TState].
+ *
+ * @see TState.combineWith
+ */
+@ExperimentalFrpApi
+fun <A, B, C, D, E, Z> combine(
+    stateA: TState<A>,
+    stateB: TState<B>,
+    stateC: TState<C>,
+    stateD: TState<D>,
+    stateE: TState<E>,
+    transform: suspend FrpScope.(A, B, C, D, E) -> Z,
+): TState<Z> {
+    val operatorName = "combine"
+    val name = operatorName
+    return TStateInit(
+        init(name) {
+            coroutineScope {
+                val dl1: Deferred<TStateImpl<A>> = async {
+                    stateA.init.connect(evalScope = this@init)
+                }
+                val dl2: Deferred<TStateImpl<B>> = async {
+                    stateB.init.connect(evalScope = this@init)
+                }
+                val dl3: Deferred<TStateImpl<C>> = async {
+                    stateC.init.connect(evalScope = this@init)
+                }
+                val dl4: Deferred<TStateImpl<D>> = async {
+                    stateD.init.connect(evalScope = this@init)
+                }
+                val dl5: Deferred<TStateImpl<E>> = async {
+                    stateE.init.connect(evalScope = this@init)
+                }
+                zipStates(
+                    name,
+                    operatorName,
+                    dl1.await(),
+                    dl2.await(),
+                    dl3.await(),
+                    dl4.await(),
+                    dl5.await(),
+                ) { a, b, c, d, e ->
+                    NoScope.runInFrpScope { transform(a, b, c, d, e) }
+                }
+            }
+        }
+    )
+}
+
 /** Returns a [TState] by applying [transform] to the value held by the original [TState]. */
 @ExperimentalFrpApi
 fun <A, B> TState<A>.flatMap(transform: suspend FrpScope.(A) -> TState<B>): TState<B> {
@@ -367,7 +418,7 @@
  * @see selector
  */
 @ExperimentalFrpApi
-class TStateSelector<A>
+class TStateSelector<in A>
 internal constructor(
     private val upstream: TState<A>,
     private val groupedChanges: GroupedTFlow<A, Boolean>,
@@ -406,6 +457,7 @@
 
     private val input: CoalescingMutableTFlow<Deferred<T>, Deferred<T>?> =
         CoalescingMutableTFlow(
+            name = null,
             coalesce = { _, new -> new },
             network = network,
             getInitialValue = { null },
@@ -423,7 +475,7 @@
                 .cached()
         state = TStateSource(name, operatorName, initialValue, calm)
         @Suppress("DeferredResultUnused")
-        network.transaction {
+        network.transaction("MutableTState.init") {
             calm.activate(evalScope = this, downstream = Schedulable.S(state))?.let {
                 (connection, needsEval) ->
                 state.upstreamConnection = connection
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/debug/Debug.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/debug/Debug.kt
index 4f302a1..0674a2e 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/debug/Debug.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/debug/Debug.kt
@@ -31,6 +31,8 @@
 import com.android.systemui.kairos.util.Just
 import com.android.systemui.kairos.util.Maybe
 import com.android.systemui.kairos.util.None
+import com.android.systemui.kairos.util.flatMap
+import com.android.systemui.kairos.util.map
 import com.android.systemui.kairos.util.none
 import com.android.systemui.kairos.util.orElseGet
 
@@ -178,3 +180,24 @@
         is TStateSource -> getStorageUnsafe()
         is DerivedMapCheap<*, *> -> none
     }
+
+private fun <A> TStateImpl<A>.getUnsafeWithEpoch(): Maybe<Pair<A, Long>> =
+    when (this) {
+        is TStateDerived -> getCachedUnsafe().map { it to invalidatedEpoch }
+        is TStateSource -> getStorageUnsafe().map { it to writeEpoch }
+        is DerivedMapCheap<*, *> -> none
+    }
+
+/**
+ * Returns the current value held in this [TState], or [none] if the [TState] has not been
+ * initialized.
+ *
+ * The returned [Long] is the *epoch* at which the internal cache was last updated. This can be used
+ * to identify values which are out-of-date.
+ */
+fun <A> TState<A>.sampleUnsafe(): Maybe<Pair<A, Long>> =
+    when (this) {
+        is MutableTState -> tState.init.getUnsafe().flatMap { it.getUnsafeWithEpoch() }
+        is TStateInit -> init.getUnsafe().flatMap { it.getUnsafeWithEpoch() }
+        is TStateLoop -> this.init.getUnsafe().flatMap { it.getUnsafeWithEpoch() }
+    }
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt
index 90f1aea..7e63849 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt
@@ -34,9 +34,9 @@
 import com.android.systemui.kairos.groupByKey
 import com.android.systemui.kairos.init
 import com.android.systemui.kairos.internal.util.childScope
-import com.android.systemui.kairos.internal.util.launchOnCancel
 import com.android.systemui.kairos.internal.util.mapValuesParallel
 import com.android.systemui.kairos.launchEffect
+import com.android.systemui.kairos.mergeLeft
 import com.android.systemui.kairos.util.Just
 import com.android.systemui.kairos.util.Maybe
 import com.android.systemui.kairos.util.None
@@ -49,7 +49,6 @@
 import kotlin.coroutines.startCoroutine
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CompletableJob
-import kotlinx.coroutines.CoroutineName
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.Job
@@ -86,8 +85,7 @@
         builder: suspend S.() -> Unit,
     ): TFlow<A> {
         var job: Job? = null
-        val stopEmitter = newStopEmitter()
-        val handle = this.job.invokeOnCompletion { stopEmitter.emit(Unit) }
+        val stopEmitter = newStopEmitter("buildTFlow")
         // Create a child scope that will be kept alive beyond the end of this transaction.
         val childScope = coroutineScope.childScope()
         lateinit var emitter: Pair<T, S>
@@ -99,7 +97,6 @@
                         reenterBuildScope(this@BuildScopeImpl, childScope).runInBuildScope {
                             launchEffect {
                                 builder(emitter.second)
-                                handle.dispose()
                                 stopEmitter.emit(Unit)
                             }
                         }
@@ -110,7 +107,7 @@
                 },
             )
         emitter = constructFlow(inputNode)
-        return with(frpScope) { emitter.first.takeUntil(stopEmitter) }
+        return with(frpScope) { emitter.first.takeUntil(mergeLeft(stopEmitter, endSignal)) }
     }
 
     private fun <T> tFlowInternal(builder: suspend FrpProducerScope<T>.() -> Unit): TFlow<T> =
@@ -134,7 +131,8 @@
     ): TFlow<Out> =
         buildTFlow(
             constructFlow = { inputNode ->
-                val flow = CoalescingMutableTFlow(coalesce, network, getInitialValue, inputNode)
+                val flow =
+                    CoalescingMutableTFlow(null, coalesce, network, getInitialValue, inputNode)
                 flow to
                     object : FrpCoalescingProducerScope<In> {
                         override fun emit(value: In) {
@@ -164,11 +162,13 @@
         val subRef = AtomicReference<Maybe<Output<A>>>(null)
         val childScope = coroutineScope.childScope()
         // When our scope is cancelled, deactivate this observer.
-        childScope.launchOnCancel(CoroutineName("TFlow.observeEffect")) {
+        childScope.coroutineContext.job.invokeOnCompletion {
             subRef.getAndSet(None)?.let { output ->
                 if (output is Just) {
                     @Suppress("DeferredResultUnused")
-                    network.transaction { scheduleDeactivation(output.value) }
+                    network.transaction("observeEffect cancelled") {
+                        scheduleDeactivation(output.value)
+                    }
                 }
             }
         }
@@ -215,7 +215,7 @@
                     } else if (needsEval) {
                         outputNode.schedule(evalScope = stateScope.evalScope)
                     }
-                } ?: childScope.cancel()
+                } ?: run { childScope.cancel() }
         }
         return childScope.coroutineContext.job
     }
@@ -229,10 +229,7 @@
                 "mapBuild",
                 mapImpl({ init.connect(evalScope = this) }) { spec ->
                         reenterBuildScope(outerScope = this@BuildScopeImpl, childScope)
-                            .runInBuildScope {
-                                val (result, _) = asyncScope { transform(spec) }
-                                result.get()
-                            }
+                            .runInBuildScope { transform(spec) }
                     }
                     .cached(),
             )
@@ -272,8 +269,9 @@
         return changes to FrpDeferredValue(initOut)
     }
 
-    private fun newStopEmitter(): CoalescingMutableTFlow<Unit, Unit> =
+    private fun newStopEmitter(name: String): CoalescingMutableTFlow<Unit, Unit> =
         CoalescingMutableTFlow(
+            name = name,
             coalesce = { _, _: Unit -> },
             network = network,
             getInitialValue = {},
@@ -299,17 +297,19 @@
     }
 
     private fun mutableChildBuildScope(): BuildScopeImpl {
-        val stopEmitter = newStopEmitter()
+        val stopEmitter = newStopEmitter("mutableChildBuildScope")
         val childScope = coroutineScope.childScope()
         childScope.coroutineContext.job.invokeOnCompletion { stopEmitter.emit(Unit) }
         // Ensure that once this transaction is done, the new child scope enters the completing
         // state (kept alive so long as there are child jobs).
-        scheduleOutput(
-            OneShot {
-                // TODO: don't like this cast
-                (childScope.coroutineContext.job as CompletableJob).complete()
-            }
-        )
+        // TODO: need to keep the scope alive if it's used to accumulate state.
+        //  Otherwise, stopEmitter will emit early, due to the call to complete().
+        //        scheduleOutput(
+        //            OneShot {
+        //                // TODO: don't like this cast
+        //                (childScope.coroutineContext.job as CompletableJob).complete()
+        //            }
+        //        )
         return BuildScopeImpl(
             stateScope = StateScopeImpl(evalScope = stateScope.evalScope, endSignal = stopEmitter),
             coroutineScope = childScope,
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt
index 3aec319..04ce5b6 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Graph.kt
@@ -86,7 +86,7 @@
     @Volatile private var dirty_depthIsDirect = true
     @Volatile private var dirty_isIndirectRoot = false
 
-    suspend fun schedule(scheduler: Scheduler, node: MuxNode<*, *, *>) {
+    fun schedule(scheduler: Scheduler, node: MuxNode<*, *, *>) {
         if (dirty_depthIsDirect) {
             scheduler.schedule(dirty_directDepth, node)
         } else {
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt
index af864e6..69994ba 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/InternalScopes.kt
@@ -66,8 +66,6 @@
 
     fun schedule(state: TStateSource<*>)
 
-    suspend fun schedule(node: MuxNode<*, *, *>)
-
     fun scheduleDeactivation(node: PushNode<*>)
 
     fun scheduleDeactivation(output: Output<*>)
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt
index f7ff15f..af68a1e 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Mux.kt
@@ -188,6 +188,14 @@
     }
 
     abstract fun hasCurrentValueLocked(transactionStore: TransactionStore): Boolean
+
+    fun schedule(evalScope: EvalScope) {
+        // TODO: Potential optimization
+        //  Detect if this node is guaranteed to have a single upstream within this transaction,
+        //  then bypass scheduling it. Instead immediately schedule its downstream and treat this
+        //  MuxNode as a Pull (effectively making it a mapCheap).
+        depthTracker.schedule(evalScope.scheduler, this)
+    }
 }
 
 /** An input branch of a mux node, associated with a key. */
@@ -202,7 +210,7 @@
         val upstreamResult = upstream.getPushEvent(evalScope)
         if (upstreamResult is Just) {
             muxNode.upstreamData[key] = upstreamResult.value
-            evalScope.schedule(muxNode)
+            muxNode.schedule(evalScope)
         }
     }
 
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
index 08bee85..3b9502a 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxDeferred.kt
@@ -409,7 +409,7 @@
                 // Schedule for evaluation if any switched-in nodes have already emitted within
                 // this transaction.
                 if (muxNode.upstreamData.isNotEmpty()) {
-                    evalScope.schedule(muxNode)
+                    muxNode.schedule(evalScope)
                 }
                 return muxNode.takeUnless { muxNode.switchedIn.isEmpty() && !isIndirect }
             }
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
index cdfafa9..b291c87 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/MuxPrompt.kt
@@ -75,7 +75,7 @@
             if (depthTracker.dirty_depthIncreased()) {
                 depthTracker.schedule(evalScope.compactor, node = this)
             }
-            evalScope.schedule(this)
+            schedule(evalScope)
         } else {
             val compactDownstream = depthTracker.isDirty()
             if (evalResult != null || compactDownstream) {
@@ -291,7 +291,7 @@
         val upstreamResult = upstream.getPushEvent(evalScope)
         if (upstreamResult is Just) {
             muxNode.patchData = upstreamResult.value
-            evalScope.schedule(muxNode)
+            muxNode.schedule(evalScope)
         }
     }
 
@@ -451,7 +451,7 @@
                     // Schedule for evaluation if any switched-in nodes or the patches node have
                     // already emitted within this transaction.
                     if (movingNode.patchData != null || movingNode.upstreamData.isNotEmpty()) {
-                        evalScope.schedule(movingNode)
+                        movingNode.schedule(evalScope)
                     }
 
                     return movingNode.takeUnless { it.patches == null && it.switchedIn.isEmpty() }
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt
index f0df89d..599b186 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt
@@ -81,11 +81,6 @@
         stateWrites.add(state)
     }
 
-    // TODO: weird that we have this *and* scheduler exposed
-    override suspend fun schedule(node: MuxNode<*, *, *>) {
-        scheduler.schedule(node.depthTracker.dirty_directDepth, node)
-    }
-
     override fun scheduleDeactivation(node: PushNode<*>) {
         deactivations.add(node)
     }
@@ -95,9 +90,7 @@
     }
 
     /** Listens for external events and starts FRP transactions. Runs forever. */
-    suspend fun runInputScheduler() = coroutineScope {
-        launch { scheduler.activate() }
-        launch { compactor.activate() }
+    suspend fun runInputScheduler() {
         val actions = mutableListOf<ScheduledAction<*>>()
         for (first in inputScheduleChan) {
             // Drain and conflate all transaction requests into a single transaction
@@ -125,12 +118,12 @@
     }
 
     /** Evaluates [block] inside of a new transaction when the network is ready. */
-    fun <R> transaction(block: suspend EvalScope.() -> R): Deferred<R> =
+    fun <R> transaction(reason: String, block: suspend EvalScope.() -> R): Deferred<R> =
         CompletableDeferred<R>(parent = coroutineScope.coroutineContext.job).also { onResult ->
             val job =
                 coroutineScope.launch {
                     inputScheduleChan.send(
-                        ScheduledAction(onStartTransaction = block, onResult = onResult)
+                        ScheduledAction(reason, onStartTransaction = block, onResult = onResult)
                     )
                 }
             onResult.invokeOnCompletion { job.cancel() }
@@ -229,6 +222,7 @@
 }
 
 internal class ScheduledAction<T>(
+    val reason: String,
     private val onResult: CompletableDeferred<T>? = null,
     private val onStartTransaction: suspend EvalScope.() -> T,
 ) {
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Scheduler.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Scheduler.kt
index 872fb7a..c12ef6a 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Scheduler.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Scheduler.kt
@@ -21,44 +21,34 @@
 import java.util.concurrent.ConcurrentHashMap
 import java.util.concurrent.PriorityBlockingQueue
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 
 internal interface Scheduler {
-    suspend fun schedule(depth: Int, node: MuxNode<*, *, *>)
+    fun schedule(depth: Int, node: MuxNode<*, *, *>)
 
-    suspend fun scheduleIndirect(indirectDepth: Int, node: MuxNode<*, *, *>)
+    fun scheduleIndirect(indirectDepth: Int, node: MuxNode<*, *, *>)
 }
 
 internal class SchedulerImpl : Scheduler {
     val enqueued = ConcurrentHashMap<MuxNode<*, *, *>, Any>()
     val scheduledQ = PriorityBlockingQueue<Pair<Int, MuxNode<*, *, *>>>(16, compareBy { it.first })
-    val chan = Channel<Pair<Int, MuxNode<*, *, *>>>(Channel.UNLIMITED)
 
-    override suspend fun schedule(depth: Int, node: MuxNode<*, *, *>) {
+    override fun schedule(depth: Int, node: MuxNode<*, *, *>) {
         if (enqueued.putIfAbsent(node, node) == null) {
-            chan.send(Pair(depth, node))
+            scheduledQ.add(Pair(depth, node))
         }
     }
 
-    override suspend fun scheduleIndirect(indirectDepth: Int, node: MuxNode<*, *, *>) {
+    override fun scheduleIndirect(indirectDepth: Int, node: MuxNode<*, *, *>) {
         schedule(Int.MIN_VALUE + indirectDepth, node)
     }
 
-    suspend fun activate() {
-        for (nodeSchedule in chan) {
-            scheduledQ.add(nodeSchedule)
-            drainChan()
-        }
-    }
-
     internal suspend fun drainEval(network: Network) {
         drain { runStep ->
             runStep { muxNode -> network.evalScope { muxNode.visit(this) } }
             // If any visited MuxPromptNodes had their depths increased, eagerly propagate those
-            // depth
-            // changes now before performing further network evaluation.
+            // depth changes now before performing further network evaluation.
             network.compactor.drainCompact()
         }
     }
@@ -71,19 +61,12 @@
         crossinline onStep:
             suspend (runStep: suspend (visit: suspend (MuxNode<*, *, *>) -> Unit) -> Unit) -> Unit
     ): Unit = coroutineScope {
-        while (!chan.isEmpty || scheduledQ.isNotEmpty()) {
-            drainChan()
+        while (scheduledQ.isNotEmpty()) {
             val maxDepth = scheduledQ.peek()?.first ?: error("Unexpected empty scheduler")
             onStep { visit -> runStep(maxDepth, visit) }
         }
     }
 
-    private suspend fun drainChan() {
-        while (!chan.isEmpty) {
-            scheduledQ.add(chan.receive())
-        }
-    }
-
     private suspend inline fun runStep(
         maxDepth: Int,
         crossinline visit: suspend (MuxNode<*, *, *>) -> Unit,
diff --git a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TStateImpl.kt b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TStateImpl.kt
index 5cec05c..c68b4c3 100644
--- a/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TStateImpl.kt
+++ b/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/TStateImpl.kt
@@ -314,6 +314,28 @@
         @Suppress("UNCHECKED_CAST") transform(a as A, b as B, c as C, d as D)
     }
 
+internal fun <A, B, C, D, E, Z> zipStates(
+    name: String?,
+    operatorName: String,
+    l1: TStateImpl<A>,
+    l2: TStateImpl<B>,
+    l3: TStateImpl<C>,
+    l4: TStateImpl<D>,
+    l5: TStateImpl<E>,
+    transform: suspend EvalScope.(A, B, C, D, E) -> Z,
+): TStateImpl<Z> =
+    zipStates(null, operatorName, mapOf(0 to l1, 1 to l2, 2 to l3, 3 to l4, 4 to l5)).map(
+        name,
+        operatorName,
+    ) {
+        val a = it.getValue(0)
+        val b = it.getValue(1)
+        val c = it.getValue(2)
+        val d = it.getValue(3)
+        val e = it.getValue(4)
+        @Suppress("UNCHECKED_CAST") transform(a as A, b as B, c as C, d as D, e as E)
+    }
+
 internal fun <K : Any, A> zipStates(
     name: String?,
     operatorName: String,
diff --git a/packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt b/packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt
index 165230b..688adae 100644
--- a/packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt
+++ b/packages/SystemUI/utils/kairos/test/com/android/systemui/kairos/KairosTests.kt
@@ -1170,12 +1170,12 @@
                                     mergeIncrementally
                                         .onEach { println("patch: $it") }
                                         .foldMapIncrementally()
-                                        .flatMap { it.combineValues() }
+                                        .flatMap { it.combine() }
                                 }
                             }
                         }
                         .foldMapIncrementally()
-                        .flatMap { it.combineValues() }
+                        .flatMap { it.combine() }
 
                 accState.toStateFlow()
             }
@@ -1300,6 +1300,26 @@
     }
 
     @Test
+    fun buildScope_stateAccumulation() = runFrpTest { network ->
+        val input = network.mutableTFlow<Unit>()
+        var observedCount: Int? = null
+        activateSpec(network) {
+            val (c, j) = asyncScope { input.fold(0) { _, x -> x + 1 } }
+            deferredBuildScopeAction { c.get().observe { observedCount = it } }
+        }
+        runCurrent()
+        assertEquals(0, observedCount)
+
+        input.emit(Unit)
+        runCurrent()
+        assertEquals(1, observedCount)
+
+        input.emit(Unit)
+        runCurrent()
+        assertEquals(2, observedCount)
+    }
+
+    @Test
     fun effect() = runFrpTest { network ->
         val input = network.mutableTFlow<Unit>()
         var effectRunning = false
diff --git a/services/appfunctions/Android.bp b/services/appfunctions/Android.bp
index eb6e468..7337aa2 100644
--- a/services/appfunctions/Android.bp
+++ b/services/appfunctions/Android.bp
@@ -19,6 +19,7 @@
     defaults: ["platform_service_defaults"],
     srcs: [
         ":services.appfunctions-sources",
+        ":statslog-appfunctions-java-gen",
         "java/**/*.logtags",
     ],
     libs: ["services.core"],
@@ -26,3 +27,10 @@
         baseline_filename: "lint-baseline.xml",
     },
 }
+
+genrule {
+    name: "statslog-appfunctions-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module appfunctions --javaPackage com.android.server.appfunctions --javaClass AppFunctionsStatsLog --minApiLevel 35",
+    out: ["java/com/android/server/appfunctions/AppFunctionsStatsLog.java"],
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
index 81e83b5..eaea443 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
@@ -16,6 +16,8 @@
 
 package com.android.server.appfunctions;
 
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
@@ -35,6 +37,11 @@
                     /* workQueue= */ new LinkedBlockingQueue<>(),
                     new NamedThreadFactory("AppFunctionExecutors"));
 
+    /** Executor for stats logging. */
+    public static final ExecutorService LOGGING_THREAD_EXECUTOR =
+            Executors.newSingleThreadExecutor(
+                    new NamedThreadFactory("AppFunctionsLoggingExecutors"));
+
     static {
         THREAD_POOL_EXECUTOR.allowCoreThreadTimeOut(true);
     }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index c17c340..9cc5a8c 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -30,6 +30,7 @@
 import android.app.appfunctions.AppFunctionRuntimeMetadata;
 import android.app.appfunctions.AppFunctionStaticMetadataHelper;
 import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
+import android.app.appfunctions.ExecuteAppFunctionResponse;
 import android.app.appfunctions.IAppFunctionEnabledCallback;
 import android.app.appfunctions.IAppFunctionManager;
 import android.app.appfunctions.IAppFunctionService;
@@ -85,6 +86,7 @@
     private final ServiceConfig mServiceConfig;
     private final Context mContext;
     private final Map<String, Object> mLocks = new WeakHashMap<>();
+    private final AppFunctionsLoggerWrapper mLoggerWrapper;
 
     public AppFunctionManagerServiceImpl(@NonNull Context context) {
         this(
@@ -93,7 +95,8 @@
                         context, IAppFunctionService.Stub::asInterface, THREAD_POOL_EXECUTOR),
                 new CallerValidatorImpl(context),
                 new ServiceHelperImpl(context),
-                new ServiceConfigImpl());
+                new ServiceConfigImpl(),
+                new AppFunctionsLoggerWrapper(context));
     }
 
     @VisibleForTesting
@@ -102,12 +105,14 @@
             RemoteServiceCaller<IAppFunctionService> remoteServiceCaller,
             CallerValidator callerValidator,
             ServiceHelper appFunctionInternalServiceHelper,
-            ServiceConfig serviceConfig) {
+            ServiceConfig serviceConfig,
+            AppFunctionsLoggerWrapper loggerWrapper) {
         mContext = Objects.requireNonNull(context);
         mRemoteServiceCaller = Objects.requireNonNull(remoteServiceCaller);
         mCallerValidator = Objects.requireNonNull(callerValidator);
         mInternalServiceHelper = Objects.requireNonNull(appFunctionInternalServiceHelper);
         mServiceConfig = serviceConfig;
+        mLoggerWrapper = loggerWrapper;
     }
 
     /** Called when the user is unlocked. */
@@ -146,8 +151,25 @@
         Objects.requireNonNull(requestInternal);
         Objects.requireNonNull(executeAppFunctionCallback);
 
+        int callingUid = Binder.getCallingUid();
+        int callingPid = Binder.getCallingPid();
+
         final SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback =
-                new SafeOneTimeExecuteAppFunctionCallback(executeAppFunctionCallback);
+                new SafeOneTimeExecuteAppFunctionCallback(executeAppFunctionCallback,
+                        new SafeOneTimeExecuteAppFunctionCallback.CompletionCallback() {
+                            @Override
+                            public void finalizeOnSuccess(
+                                    @NonNull ExecuteAppFunctionResponse result) {
+                                mLoggerWrapper.logAppFunctionSuccess(requestInternal, result,
+                                        callingUid);
+                            }
+
+                            @Override
+                            public void finalizeOnError(@NonNull AppFunctionException error) {
+                                mLoggerWrapper.logAppFunctionError(requestInternal,
+                                        error.getErrorCode(), callingUid);
+                            }
+                        });
 
         String validatedCallingPackage;
         try {
@@ -162,9 +184,6 @@
             return null;
         }
 
-        int callingUid = Binder.getCallingUid();
-        int callingPid = Binder.getCallingPid();
-
         ICancellationSignal localCancelTransport = CancellationSignal.createTransport();
 
         THREAD_POOL_EXECUTOR.execute(
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionsLoggerWrapper.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionsLoggerWrapper.java
new file mode 100644
index 0000000..b59915a
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionsLoggerWrapper.java
@@ -0,0 +1,74 @@
+/*
+ * 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.appfunctions;
+
+import static com.android.server.appfunctions.AppFunctionExecutors.LOGGING_THREAD_EXECUTOR;
+
+import android.annotation.NonNull;
+import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
+import android.app.appfunctions.ExecuteAppFunctionResponse;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import java.util.Objects;
+
+/** Wraps AppFunctionsStatsLog. */
+public class AppFunctionsLoggerWrapper {
+    private static final String TAG = AppFunctionsLoggerWrapper.class.getSimpleName();
+
+    private static final int SUCCESS_RESPONSE_CODE = -1;
+
+    private final Context mContext;
+
+    public AppFunctionsLoggerWrapper(@NonNull Context context) {
+        mContext = Objects.requireNonNull(context);
+    }
+
+    void logAppFunctionSuccess(ExecuteAppFunctionAidlRequest request,
+            ExecuteAppFunctionResponse response, int callingUid) {
+        logAppFunctionsRequestReported(request, SUCCESS_RESPONSE_CODE,
+                response.getResponseDataSize(), callingUid);
+    }
+
+    void logAppFunctionError(ExecuteAppFunctionAidlRequest request, int errorCode, int callingUid) {
+        logAppFunctionsRequestReported(request, errorCode, /* responseSizeBytes = */ 0, callingUid);
+    }
+
+    private void logAppFunctionsRequestReported(ExecuteAppFunctionAidlRequest request,
+            int errorCode, int responseSizeBytes, int callingUid) {
+        final long latency = SystemClock.elapsedRealtime() - request.getRequestTime();
+        LOGGING_THREAD_EXECUTOR.execute(() -> AppFunctionsStatsLog.write(
+                AppFunctionsStatsLog.APP_FUNCTIONS_REQUEST_REPORTED,
+                callingUid,
+                getPackageUid(request.getClientRequest().getTargetPackageName()),
+                errorCode,
+                request.getClientRequest().getRequestDataSize(), responseSizeBytes,
+                latency)
+        );
+    }
+
+    private int getPackageUid(String packageName) {
+        try {
+            return mContext.getPackageManager().getPackageUid(packageName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(TAG, "Package uid not found for " + packageName);
+        }
+        return 0;
+    }
+}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
index c689bb9..896c0fc 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
@@ -16,8 +16,8 @@
 package com.android.server.appfunctions;
 
 import android.annotation.NonNull;
-import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
 import android.app.appfunctions.AppFunctionException;
+import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
 import android.app.appfunctions.ExecuteAppFunctionResponse;
 import android.app.appfunctions.IAppFunctionService;
 import android.app.appfunctions.ICancellationCallback;
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 883e09f..87f87c7 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -143,6 +143,7 @@
         "tv_os",
         "aaos_carframework_triage",
         "aaos_performance_triage",
+        "aaos_input_triage",
         "aaos_user_triage",
         "aaos_window_triage",
         "aaos_audio_triage",
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 09b8e21..1799b77 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4177,6 +4177,12 @@
         // Stream mute changed, fire the intent.
         Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION);
         intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, isMuted);
+        if (replaceStreamBtSco() && isStreamBluetoothSco(streamType)) {
+            intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                    AudioSystem.STREAM_BLUETOOTH_SCO);
+            // in this case broadcast for both sco and voice_call streams the mute status
+            sendBroadcastToAll(intent, null /* options */);
+        }
         intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType);
         sendBroadcastToAll(intent, null /* options */);
     }
@@ -9670,9 +9676,16 @@
                         mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
                         mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE,
                                 oldIndex);
-
-                        mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
-                                mStreamType);
+                        int extraStreamType = mStreamType;
+                        // TODO: remove this when deprecating STREAM_BLUETOOTH_SCO
+                        if (isStreamBluetoothSco(mStreamType)) {
+                            mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                                    AudioSystem.STREAM_BLUETOOTH_SCO);
+                            extraStreamType = AudioSystem.STREAM_BLUETOOTH_SCO;
+                        } else {
+                            mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                                    mStreamType);
+                        }
                         mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
                                 streamAlias);
 
@@ -9683,9 +9696,21 @@
                                         " aliased streams: " + aliasStreamIndexes;
                             }
                             AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent(
-                                    mStreamType, aliasStreamIndexesString, index, oldIndex));
+                                    extraStreamType, aliasStreamIndexesString, index, oldIndex));
+                            if (extraStreamType != mStreamType) {
+                                AudioService.sVolumeLogger.enqueue(new VolChangedBroadcastEvent(
+                                        mStreamType, aliasStreamIndexesString, index, oldIndex));
+                            }
                         }
                         sendBroadcastToAll(mVolumeChanged, mVolumeChangedOptions);
+                        if (extraStreamType != mStreamType) {
+                            // send multiple intents in case we merged voice call and bt sco streams
+                            mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+                                    mStreamType);
+                            // do not use the options in thid case which could discard
+                            // the previous intent
+                            sendBroadcastToAll(mVolumeChanged, null);
+                        }
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 945365d..f48fbea 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -906,6 +906,11 @@
                 mLogicalDisplay.getPowerThrottlingDataIdLocked();
 
         mHandler.postAtTime(() -> {
+            if (mStopped) {
+                // DPC has already stopped, don't execute any more.
+                return;
+            }
+
             boolean changed = false;
 
             if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) {
@@ -3306,7 +3311,7 @@
                 int displayId, SensorManager sensorManager) {
             return new DisplayPowerProximityStateController(wakelockController, displayDeviceConfig,
                     looper, nudgeUpdatePowerState,
-                    displayId, sensorManager, /* injector= */ null);
+                    displayId, sensorManager);
         }
 
         AutomaticBrightnessController getAutomaticBrightnessController(
diff --git a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
index 215932c..35455c8 100644
--- a/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerProximityStateController.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
@@ -34,6 +36,8 @@
 import com.android.server.display.utils.SensorUtils;
 
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Maintains the proximity state of the display.
@@ -42,18 +46,26 @@
  */
 public final class DisplayPowerProximityStateController {
     @VisibleForTesting
-    static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 1;
-    @VisibleForTesting
     static final int PROXIMITY_UNKNOWN = -1;
+    private static final int PROXIMITY_NEGATIVE = 0;
     @VisibleForTesting
     static final int PROXIMITY_POSITIVE = 1;
+
+    @IntDef(prefix = { "PROXIMITY_" }, value = {
+            PROXIMITY_UNKNOWN,
+            PROXIMITY_NEGATIVE,
+            PROXIMITY_POSITIVE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ProximityState {}
+
+    @VisibleForTesting
+    static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 1;
     @VisibleForTesting
     static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
 
     private static final int MSG_IGNORE_PROXIMITY = 2;
 
-    private static final int PROXIMITY_NEGATIVE = 0;
-
     private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
     // Proximity sensor debounce delay in milliseconds for positive transitions.
 
@@ -73,7 +85,7 @@
     private final DisplayPowerProximityStateHandler mHandler;
     // A runnable to execute the utility to update the power state.
     private final Runnable mNudgeUpdatePowerState;
-    private Clock mClock;
+    private final Clock mClock;
     // A listener which listen's to the events emitted by the proximity sensor.
     private final SensorEventListener mProximitySensorListener = new SensorEventListener() {
         @Override
@@ -117,9 +129,6 @@
     // with the sensor manager.
     private boolean mProximitySensorEnabled;
 
-    // The raw non-debounced proximity sensor state.
-    private int mPendingProximity = PROXIMITY_UNKNOWN;
-
     // -1 if fully debounced. Else, represents the time in ms when the debounce suspend blocker will
     // be removed. Applies for both positive and negative proximity flips.
     private long mPendingProximityDebounceTime = -1;
@@ -128,8 +137,11 @@
     // When the screen turns on again, we report user activity to the power manager.
     private boolean mScreenOffBecauseOfProximity;
 
+    // The raw non-debounced proximity sensor state.
+    private @ProximityState int mPendingProximity = PROXIMITY_UNKNOWN;
+
     // The debounced proximity sensor state.
-    private int mProximity = PROXIMITY_UNKNOWN;
+    private @ProximityState int mProximity = PROXIMITY_UNKNOWN;
 
     // The actual proximity sensor threshold value.
     private float mProximityThreshold;
@@ -139,7 +151,7 @@
     private boolean mSkipRampBecauseOfProximityChangeToNegative = false;
 
     // The DisplayId of the associated Logical Display.
-    private int mDisplayId;
+    private final int mDisplayId;
 
     /**
      * Create a new instance of DisplayPowerProximityStateController.
@@ -152,11 +164,18 @@
      * @param displayId             The DisplayId of the associated Logical Display.
      * @param sensorManager         The manager which lets us access the display's ProximitySensor
      */
-    public DisplayPowerProximityStateController(
-            WakelockController wakeLockController, DisplayDeviceConfig displayDeviceConfig,
-            Looper looper,
+    public DisplayPowerProximityStateController(WakelockController wakeLockController,
+            DisplayDeviceConfig displayDeviceConfig, Looper looper,
+            Runnable nudgeUpdatePowerState, int displayId, SensorManager sensorManager) {
+        this(wakeLockController, displayDeviceConfig, looper, nudgeUpdatePowerState, displayId,
+                sensorManager, new Injector());
+    }
+
+    @VisibleForTesting
+    DisplayPowerProximityStateController(WakelockController wakeLockController,
+            DisplayDeviceConfig displayDeviceConfig, Looper looper,
             Runnable nudgeUpdatePowerState, int displayId, SensorManager sensorManager,
-            Injector injector) {
+            @Nullable Injector injector) {
         if (injector == null) {
             injector = new Injector();
         }
@@ -437,7 +456,7 @@
                 if (mProximity != mPendingProximity) {
                     // if the status of the sensor changed, stop ignoring.
                     mIgnoreProximityUntilChanged = false;
-                    Slog.i(mTag, "No longer ignoring proximity [" + mPendingProximity + "]");
+                    Slog.i(mTag, "Applying proximity: " + proximityToString(mPendingProximity));
                 }
                 // Sensor reading accepted.  Apply the change then release the wake lock.
                 mProximity = mPendingProximity;
@@ -478,7 +497,7 @@
         }
     }
 
-    private String proximityToString(int state) {
+    private String proximityToString(@ProximityState int state) {
         switch (state) {
             case PROXIMITY_UNKNOWN:
                 return "Unknown";
@@ -518,12 +537,12 @@
     }
 
     @VisibleForTesting
-    int getPendingProximity() {
+    @ProximityState int getPendingProximity() {
         return mPendingProximity;
     }
 
     @VisibleForTesting
-    int getProximity() {
+    @ProximityState int getProximity() {
         return mProximity;
     }
 
@@ -550,7 +569,7 @@
     @VisibleForTesting
     static class Injector {
         Clock createClock() {
-            return () -> SystemClock.uptimeMillis();
+            return SystemClock::uptimeMillis;
         }
     }
 }
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index c0903a9..79592a65 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -506,9 +506,6 @@
             return;
         }
 
-        Slog.i(TAG, "Requesting Transition to state: " + state.getIdentifier() + ", from state="
-                + mDeviceState.getIdentifier() + ", interactive=" + mInteractive
-                + ", mBootCompleted=" + mBootCompleted);
         // As part of a state transition, we may need to turn off some displays temporarily so that
         // the transition is smooth. Plus, on some devices, only one internal displays can be
         // on at a time. We use LogicalDisplay.setIsInTransition to mark a display that needs to be
@@ -522,6 +519,11 @@
         final boolean sleepDevice = shouldDeviceBePutToSleep(mPendingDeviceState, mDeviceState,
                 mInteractive, mBootCompleted);
 
+        Slog.i(TAG, "Requesting Transition to state: " + state.getIdentifier() + ", from state="
+                + mDeviceState.getIdentifier() + ", interactive=" + mInteractive
+                + ", mBootCompleted=" + mBootCompleted + ", wakeDevice=" + wakeDevice
+                + ", sleepDevice=" + sleepDevice);
+
         // If all displays are off already, we can just transition here, unless we are trying to
         // wake or sleep the device as part of this transition. In that case defer the final
         // transition until later once the device is awake/asleep.
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index db1e6b4..5ee9452 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -66,6 +66,7 @@
     private final int mUserId;
     private final Handler mHandler;
     private final boolean mIsSelfScanOnlyProvider;
+    private final boolean mSupportsSystemMediaRouting;
     private final ServiceConnection mServiceConnection = new ServiceConnectionImpl();
 
     // Connection state
@@ -95,12 +96,14 @@
             @NonNull Looper looper,
             @NonNull ComponentName componentName,
             boolean isSelfScanOnlyProvider,
+            boolean supportsSystemMediaRouting,
             int userId) {
         super(componentName, /* isSystemRouteProvider= */ false);
         mContext = Objects.requireNonNull(context, "Context must not be null.");
         mRequestIdToSessionCreationRequest = new LongSparseArray<>();
         mSessionOriginalIdToTransferRequest = new HashMap<>();
         mIsSelfScanOnlyProvider = isSelfScanOnlyProvider;
+        mSupportsSystemMediaRouting = supportsSystemMediaRouting;
         mUserId = userId;
         mHandler = new Handler(looper);
     }
@@ -651,11 +654,12 @@
         }
         return TextUtils.formatSimple(
                 "ProviderServiceProxy - package: %s, bound: %b, connection (active:%b, ready:%b), "
-                        + "pending (session creations: %d, transfers: %d)",
+                        + "system media=%b, pending (session creations: %d, transfers: %d)",
                 mComponentName.getPackageName(),
                 mBound,
                 mActiveConnection != null,
                 mConnectionReady,
+                mSupportsSystemMediaRouting,
                 pendingSessionCreationCount,
                 pendingTransferCount);
     }
@@ -697,7 +701,7 @@
 
         Connection(IMediaRoute2ProviderService serviceBinder) {
             mService = serviceBinder;
-            mCallbackStub = new ServiceCallbackStub(this);
+            mCallbackStub = new ServiceCallbackStub(this, mSupportsSystemMediaRouting);
         }
 
         public boolean register() {
@@ -811,9 +815,11 @@
     private static final class ServiceCallbackStub extends
             IMediaRoute2ProviderServiceCallback.Stub {
         private final WeakReference<Connection> mConnectionRef;
+        private final boolean mAllowSystemMediaRoutes;
 
-        ServiceCallbackStub(Connection connection) {
+        ServiceCallbackStub(Connection connection, boolean allowSystemMediaRoutes) {
             mConnectionRef = new WeakReference<>(connection);
+            mAllowSystemMediaRoutes = allowSystemMediaRoutes;
         }
 
         public void dispose() {
@@ -846,6 +852,13 @@
                                     + "Disallowed route: "
                                     + route);
                 }
+
+                if (route.supportsSystemMediaRouting() && !mAllowSystemMediaRoutes) {
+                    throw new SecurityException(
+                            "This provider is not allowed to publish routes that support system"
+                                    + " media routing. Disallowed route: "
+                                    + route);
+                }
             }
 
             Connection connection = mConnectionRef.get();
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index 93ef6f0..69c460e 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -17,7 +17,9 @@
 package com.android.server.media;
 
 import static android.content.pm.PackageManager.GET_RESOLVED_FILTER;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -130,22 +132,33 @@
             ServiceInfo serviceInfo = resolveInfo.serviceInfo;
             if (serviceInfo != null) {
                 boolean isSelfScanOnlyProvider = false;
+                boolean supportsSystemMediaRouting = false;
                 Iterator<String> categoriesIterator = resolveInfo.filter.categoriesIterator();
                 if (categoriesIterator != null) {
                     while (categoriesIterator.hasNext()) {
+                        String category = categoriesIterator.next();
                         isSelfScanOnlyProvider |=
-                                MediaRoute2ProviderService.CATEGORY_SELF_SCAN_ONLY.equals(
-                                        categoriesIterator.next());
+                                MediaRoute2ProviderService.CATEGORY_SELF_SCAN_ONLY.equals(category);
+                        supportsSystemMediaRouting |=
+                                MediaRoute2ProviderService.SERVICE_INTERFACE_SYSTEM_MEDIA.equals(
+                                        category);
                     }
                 }
                 int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
                 if (sourceIndex < 0) {
+                    supportsSystemMediaRouting &= Flags.enableMirroringInMediaRouter2();
+                    supportsSystemMediaRouting &=
+                            mPackageManager.checkPermission(
+                                            Manifest.permission.MODIFY_AUDIO_ROUTING,
+                                            serviceInfo.packageName)
+                                    == PERMISSION_GRANTED;
                     MediaRoute2ProviderServiceProxy proxy =
                             new MediaRoute2ProviderServiceProxy(
                                     mContext,
                                     mHandler.getLooper(),
                                     new ComponentName(serviceInfo.packageName, serviceInfo.name),
                                     isSelfScanOnlyProvider,
+                                    supportsSystemMediaRouting,
                                     mUserId);
                     Slog.i(
                             TAG,
diff --git a/services/core/java/com/android/server/pm/InstallDependencyHelper.java b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
index c0ddebe..837adf0 100644
--- a/services/core/java/com/android/server/pm/InstallDependencyHelper.java
+++ b/services/core/java/com/android/server/pm/InstallDependencyHelper.java
@@ -78,24 +78,22 @@
         mPackageInstallerService = packageInstallerService;
     }
 
-    void resolveLibraryDependenciesIfNeeded(PackageLite pkg, Computer snapshot, int userId,
-            Handler handler, OutcomeReceiver<Void, PackageManagerException> origCallback) {
+    void resolveLibraryDependenciesIfNeeded(List<SharedLibraryInfo> missingLibraries,
+            PackageLite pkg, Computer snapshot, int userId, Handler handler,
+            OutcomeReceiver<Void, PackageManagerException> origCallback) {
         CallOnceProxy callback = new CallOnceProxy(handler, origCallback);
         try {
-            resolveLibraryDependenciesIfNeededInternal(pkg, snapshot, userId, handler, callback);
-        } catch (PackageManagerException e) {
-            callback.onError(e);
+            resolveLibraryDependenciesIfNeededInternal(
+                    missingLibraries, pkg, snapshot, userId, handler, callback);
         } catch (Exception e) {
             onError(callback, e.getMessage());
         }
     }
 
 
-    private void resolveLibraryDependenciesIfNeededInternal(PackageLite pkg, Computer snapshot,
-            int userId, Handler handler, CallOnceProxy callback) throws PackageManagerException {
-        final List<SharedLibraryInfo> missing =
-                mSharedLibraries.collectMissingSharedLibraryInfos(pkg);
-
+    private void resolveLibraryDependenciesIfNeededInternal(List<SharedLibraryInfo> missing,
+            PackageLite pkg, Computer snapshot, int userId, Handler handler,
+            CallOnceProxy callback) {
         if (missing.isEmpty()) {
             if (DEBUG) {
                 Slog.d(TAG, "No missing dependency for " + pkg.getPackageName());
@@ -129,6 +127,11 @@
         }
     }
 
+    List<SharedLibraryInfo> getMissingSharedLibraries(PackageLite pkg)
+            throws PackageManagerException {
+        return mSharedLibraries.collectMissingSharedLibraryInfos(pkg);
+    }
+
     void notifySessionComplete(int sessionId) {
         if (DEBUG) {
             Slog.d(TAG, "Session complete for " + sessionId);
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index b0fe3a9..c96c160 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -170,6 +170,8 @@
     private final boolean mHasAppMetadataFileFromInstaller;
 
     private boolean mKeepArtProfile = false;
+    private final boolean mDependencyInstallerEnabled;
+    private final int mMissingSharedLibraryCount;
 
     // New install
     InstallRequest(InstallingSession params) {
@@ -190,6 +192,8 @@
         mRequireUserAction = params.mRequireUserAction;
         mPreVerifiedDomains = params.mPreVerifiedDomains;
         mHasAppMetadataFileFromInstaller = params.mHasAppMetadataFile;
+        mDependencyInstallerEnabled = params.mDependencyInstallerEnabled;
+        mMissingSharedLibraryCount = params.mMissingSharedLibraryCount;
     }
 
     // Install existing package as user
@@ -209,6 +213,8 @@
         mInstallerUidForInstallExisting = installerUid;
         mSystem = isSystem;
         mHasAppMetadataFileFromInstaller = false;
+        mDependencyInstallerEnabled = false;
+        mMissingSharedLibraryCount = 0;
     }
 
     // addForInit
@@ -231,6 +237,8 @@
         mRequireUserAction = USER_ACTION_UNSPECIFIED;
         mDisabledPs = disabledPs;
         mHasAppMetadataFileFromInstaller = false;
+        mDependencyInstallerEnabled = false;
+        mMissingSharedLibraryCount = 0;
     }
 
     @Nullable
@@ -1069,4 +1077,12 @@
     boolean isKeepArtProfile() {
         return mKeepArtProfile;
     }
+
+    int getMissingSharedLibraryCount() {
+        return mMissingSharedLibraryCount;
+    }
+
+    boolean isDependencyInstallerEnabled() {
+        return mDependencyInstallerEnabled;
+    }
 }
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index ccc1175..6a2bf83 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -103,6 +103,8 @@
     final DomainSet mPreVerifiedDomains;
     final boolean mHasAppMetadataFile;
     @Nullable final String mDexoptCompilerFilter;
+    final boolean mDependencyInstallerEnabled;
+    final int mMissingSharedLibraryCount;
 
     // For move install
     InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer,
@@ -138,13 +140,16 @@
         mPreVerifiedDomains = null;
         mHasAppMetadataFile = false;
         mDexoptCompilerFilter = null;
+        mDependencyInstallerEnabled = false;
+        mMissingSharedLibraryCount = 0;
     }
 
     InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer,
             PackageInstaller.SessionParams sessionParams, InstallSource installSource,
             UserHandle user, SigningDetails signingDetails, int installerUid,
             PackageLite packageLite, DomainSet preVerifiedDomains, PackageManagerService pm,
-            boolean hasAppMetadatafile) {
+            boolean hasAppMetadatafile, boolean dependencyInstallerEnabled,
+            int missingSharedLibraryCount) {
         mPm = pm;
         mUser = user;
         mOriginInfo = OriginInfo.fromStagedFile(stagedDir);
@@ -175,6 +180,8 @@
         mPreVerifiedDomains = preVerifiedDomains;
         mHasAppMetadataFile = hasAppMetadatafile;
         mDexoptCompilerFilter = sessionParams.dexoptCompilerFilter;
+        mDependencyInstallerEnabled = dependencyInstallerEnabled;
+        mMissingSharedLibraryCount = missingSharedLibraryCount;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 891d66a..c676043 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -31,6 +31,7 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
 import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
 import static android.content.pm.PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
 import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
 import static android.content.pm.PackageManager.INSTALL_FAILED_PRE_APPROVAL_NOT_AVAILABLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_SESSION_INVALID;
@@ -109,6 +110,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.PackageInfoFlags;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SigningDetails;
 import android.content.pm.dex.DexMetadataHelper;
 import android.content.pm.parsing.ApkLite;
@@ -540,6 +542,9 @@
     @GuardedBy("mLock")
     private DomainSet mPreVerifiedDomains;
 
+    private AtomicBoolean mDependencyInstallerEnabled = new AtomicBoolean();
+    private AtomicInteger mMissingSharedLibraryCount = new AtomicInteger();
+
     static class FileEntry {
         private final int mIndex;
         private final InstallationFile mFile;
@@ -3232,6 +3237,7 @@
         if (Flags.sdkDependencyInstaller()
                 && params.isAutoInstallDependenciesEnabled
                 && !isMultiPackage()) {
+            mDependencyInstallerEnabled.set(true);
             resolveLibraryDependenciesIfNeeded();
         } else {
             install();
@@ -3241,8 +3247,20 @@
 
     private void resolveLibraryDependenciesIfNeeded() {
         synchronized (mLock) {
-            mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(mPackageLite,
-                    mPm.snapshotComputer(), userId, mHandler,
+            List<SharedLibraryInfo> missingLibraries = new ArrayList<>();
+            try {
+                missingLibraries = mInstallDependencyHelper.getMissingSharedLibraries(mPackageLite);
+            } catch (PackageManagerException e) {
+                handleDependencyResolutionFailure(e);
+            } catch (Exception e) {
+                handleDependencyResolutionFailure(
+                        new PackageManagerException(
+                                INSTALL_FAILED_MISSING_SHARED_LIBRARY, e.getMessage()));
+            }
+
+            mMissingSharedLibraryCount.set(missingLibraries.size());
+            mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(missingLibraries,
+                    mPackageLite, mPm.snapshotComputer(), userId, mHandler,
                     new OutcomeReceiver<>() {
 
                         @Override
@@ -3252,14 +3270,21 @@
 
                         @Override
                         public void onError(@NonNull PackageManagerException e) {
-                            final String completeMsg = ExceptionUtils.getCompleteMessage(e);
-                            setSessionFailed(e.error, completeMsg);
-                            onSessionDependencyResolveFailure(e.error, completeMsg);
+                            handleDependencyResolutionFailure(e);
                         }
                     });
         }
     }
 
+    private void handleDependencyResolutionFailure(@NonNull PackageManagerException e) {
+        final String completeMsg = ExceptionUtils.getCompleteMessage(e);
+        setSessionFailed(e.error, completeMsg);
+        onSessionDependencyResolveFailure(e.error, completeMsg);
+        PackageMetrics.onDependencyInstallationFailure(
+                sessionId, getPackageName(), e.error, mInstallerUid, params,
+                mMissingSharedLibraryCount.get());
+    }
+
     /**
      * Stages this session for install and returns a
      * {@link InstallingSession} representing this new staged state.
@@ -3327,7 +3352,8 @@
         synchronized (mLock) {
             return new InstallingSession(sessionId, stageDir, localObserver, params, mInstallSource,
                     user, mSigningDetails, mInstallerUid, mPackageLite, mPreVerifiedDomains, mPm,
-                    mHasAppMetadataFile);
+                    mHasAppMetadataFile, mDependencyInstallerEnabled.get(),
+                    mMissingSharedLibraryCount.get());
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 0acadb1..856d6a7 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -32,7 +32,9 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.pm.DataLoaderType;
 import android.content.pm.Flags;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.parsing.ApkLiteParseUtils;
@@ -173,7 +175,10 @@
                 mInstallRequest.isInstallInherit() /* is_inherit */,
                 mInstallRequest.isInstallForUsers() /* is_installing_existing_as_user */,
                 mInstallRequest.isInstallMove() /* is_move_install */,
-                false /* is_staged */
+                false /* is_staged */,
+                mInstallRequest
+                        .isDependencyInstallerEnabled() /* is_install_dependencies_enabled */,
+                mInstallRequest.getMissingSharedLibraryCount() /* missing_dependencies_count */
         );
     }
 
@@ -323,7 +328,53 @@
                 verifyingSession.isInherit() /* is_inherit */,
                 false /* is_installing_existing_as_user */,
                 false /* is_move_install */,
-                verifyingSession.isStaged() /* is_staged */
+                verifyingSession.isStaged() /* is_staged */,
+                false /* is_install_dependencies_enabled */,
+                0 /* missing_dependencies_count */
+        );
+    }
+
+    static void onDependencyInstallationFailure(
+            int sessionId, String packageName, int errorCode, int installerPackageUid,
+            PackageInstaller.SessionParams params, int missingDependenciesCount) {
+        if (params == null) {
+            return;
+        }
+        int dataLoaderType = DataLoaderType.NONE;
+        if (params.dataLoaderParams != null) {
+            dataLoaderType = params.dataLoaderParams.getType();
+        }
+
+        FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLATION_SESSION_REPORTED,
+                sessionId /* session_id */,
+                packageName /* package_name */,
+                INVALID_UID /* uid */,
+                null /* user_ids */,
+                null /* user_types */,
+                null /* original_user_ids */,
+                null /* original_user_types */,
+                errorCode /* public_return_code */,
+                0 /* internal_error_code */,
+                0 /* apks_size_bytes */,
+                0 /* version_code */,
+                null /* install_steps */,
+                null /* step_duration_millis */,
+                0 /* total_duration_millis */,
+                0 /* install_flags */,
+                installerPackageUid /* installer_package_uid */,
+                INVALID_UID /* original_installer_package_uid */,
+                dataLoaderType /* data_loader_type */,
+                params.requireUserAction /* user_action_required_type */,
+                (params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0 /* is_instant */,
+                false /* is_replace */,
+                false /* is_system */,
+                params.mode
+                        == PackageInstaller.SessionParams.MODE_INHERIT_EXISTING /* is_inherit */,
+                false /* is_installing_existing_as_user */,
+                false /* is_move_install */,
+                params.isStaged /* is_staged */,
+                true /* is_install_dependencies_enabled */,
+                missingDependenciesCount /* missing_dependencies_count */
         );
     }
 
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index 542ae8e..dd60a15 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -16,11 +16,7 @@
 
 package com.android.server.pm;
 
-import static android.content.Intent.EXTRA_LONG_VERSION_CODE;
-import static android.content.Intent.EXTRA_PACKAGE_NAME;
-import static android.content.Intent.EXTRA_VERSION_CODE;
 import static android.content.pm.PackageInstaller.SessionParams.MODE_INHERIT_EXISTING;
-import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED;
 import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
 import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4;
@@ -40,9 +36,7 @@
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.DataLoaderType;
@@ -68,7 +62,6 @@
 import android.os.UserManager;
 import android.os.incremental.IncrementalManager;
 import android.provider.DeviceConfig;
-import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Pair;
 import android.util.Slog;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 36bc0b9..ce8dc69 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -7093,7 +7093,11 @@
                     if ((flags & PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP) != 0) {
                         if (mFoldGracePeriodProvider.isEnabled()) {
                             if (!powerGroup.hasWakeLockKeepingScreenOnLocked()) {
+                                Slog.d(TAG, "Showing dismissible keyguard");
                                 mNotifier.showDismissibleKeyguard();
+                            } else {
+                                Slog.i(TAG, "There is a screen wake lock present: "
+                                        + "sleep request will be ignored");
                             }
                             continue; // never actually goes to sleep for SOFT_SLEEP
                         } else {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3467f94..5989fc8 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3212,8 +3212,7 @@
      * will be ignored.
      */
     boolean isUniversalResizeable() {
-        final boolean isLargeScreen = mDisplayContent != null && mDisplayContent.getConfiguration()
-                .smallestScreenWidthDp >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
+        final boolean isLargeScreen = mDisplayContent != null && mDisplayContent.isLargeScreen()
                 && mDisplayContent.getIgnoreOrientationRequest();
         if (!canBeUniversalResizeable(info.applicationInfo, mWmService, isLargeScreen,
                 true /* forActivity */)) {
@@ -4585,6 +4584,19 @@
         }
     }
 
+    /**
+     * Returns {@code true} if the requested orientation of this activity is the same as the
+     * resolved orientation of the from activity.
+     */
+    private boolean isStartingOrientationCompatible(@NonNull ActivityRecord fromActivity) {
+        final int fromOrientation = fromActivity.getConfiguration().orientation;
+        final int requestedOrientation = getRequestedConfigurationOrientation();
+        if (requestedOrientation == ORIENTATION_UNDEFINED) {
+            return fromOrientation == getConfiguration().orientation;
+        }
+        return fromOrientation == requestedOrientation;
+    }
+
     private boolean transferStartingWindow(@NonNull ActivityRecord fromActivity) {
         final WindowState tStartingWindow = fromActivity.mStartingWindow;
         if (tStartingWindow != null && fromActivity.mStartingSurface != null) {
@@ -4604,13 +4616,7 @@
             }
             // Do not transfer if the orientation doesn't match, redraw starting window while it is
             // on top will cause flicker.
-            final int fromOrientation = fromActivity.getConfiguration().orientation;
-            final int requestedOrientation = getRequestedConfigurationOrientation();
-            if (requestedOrientation == ORIENTATION_UNDEFINED) {
-                if (fromOrientation != getConfiguration().orientation) {
-                    return false;
-                }
-            } else if (fromOrientation != requestedOrientation) {
+            if (!isStartingOrientationCompatible(fromActivity)) {
                 return false;
             }
 
@@ -4708,6 +4714,11 @@
             }
             return true;
         } else if (fromActivity.mStartingData != null) {
+            if (fromActivity.mStartingData instanceof SnapshotStartingData
+                    && !isStartingOrientationCompatible(fromActivity)) {
+                // Do not transfer because the snapshot will be distorted in different orientation.
+                return false;
+            }
             // The previous app was getting ready to show a
             // starting window, but hasn't yet done so.  Steal it!
             ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
@@ -5408,10 +5419,16 @@
     }
 
     void setDeferHidingClient() {
+        if (Flags.removeDeferHidingClient()) {
+            return;
+        }
         mDeferHidingClient = true;
     }
 
     void clearDeferHidingClient() {
+        if (Flags.removeDeferHidingClient()) {
+            return;
+        }
         if (!mDeferHidingClient) return;
         mDeferHidingClient = false;
         if (!mVisibleRequested) {
@@ -7141,9 +7158,11 @@
 
     @Override
     void setClientVisible(boolean clientVisible) {
-        // TODO(shell-transitions): Remove mDeferHidingClient once everything is shell-transitions.
-        //                          pip activities should just remain in clientVisible.
-        if (!clientVisible && mDeferHidingClient) return;
+        if (!Flags.removeDeferHidingClient()) {
+            // TODO(shell-transitions): Remove mDeferHidingClient once everything is
+            //  shell-transitions. pip activities should just remain in clientVisible.
+            if (!clientVisible && mDeferHidingClient) return;
+        }
         super.setClientVisible(clientVisible);
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
index 1208b6ef..08ceb61 100644
--- a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
+++ b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
@@ -142,6 +142,8 @@
     /** Used by {@link ActivityRecord#dump}. */
     @Override
     public String toString() {
-        return String.valueOf(mConnections);
+        synchronized (mActivity) {
+            return String.valueOf(mConnections);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index c2141a7..f808661 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -439,6 +439,10 @@
      */
     private boolean mSandboxDisplayApis = true;
 
+    /** Whether {@link #setIgnoreOrientationRequest} is called to override the default policy. */
+    @VisibleForTesting
+    boolean mHasSetIgnoreOrientationRequest;
+
     /**
      * Overridden display density for current user. Initialized with {@link #mInitialDisplayDensity}
      * but can be set from Settings or via shell command "adb shell wm density".
@@ -6722,8 +6726,25 @@
         return mDisplayPolicy.getSystemUiContext();
     }
 
+    /** Returns {@code} true if the smallest screen width dp >= 600. */
+    boolean isLargeScreen() {
+        return getConfiguration().smallestScreenWidthDp
+                >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP;
+    }
+
+    @Override
+    boolean getIgnoreOrientationRequest() {
+        if (mHasSetIgnoreOrientationRequest
+                || !com.android.window.flags.Flags.universalResizableByDefault()) {
+            return super.getIgnoreOrientationRequest();
+        }
+        // Large screen (sw >= 600dp) ignores orientation request by default.
+        return isLargeScreen() && !mWmService.isIgnoreOrientationRequestDisabled();
+    }
+
     @Override
     boolean setIgnoreOrientationRequest(boolean ignoreOrientationRequest) {
+        mHasSetIgnoreOrientationRequest = true;
         if (mSetIgnoreOrientationRequest == ignoreOrientationRequest) return false;
         final boolean rotationChanged = super.setIgnoreOrientationRequest(ignoreOrientationRequest);
         mWmService.mDisplayWindowSettings.setIgnoreOrientationRequest(
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index f6d05d0..f0ba822 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -374,9 +374,9 @@
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
 
-        final boolean ignoreOrientationRequest = settings.mIgnoreOrientationRequest != null
-                ? settings.mIgnoreOrientationRequest : false;
-        dc.setIgnoreOrientationRequest(ignoreOrientationRequest);
+        if (settings.mIgnoreOrientationRequest != null) {
+            dc.setIgnoreOrientationRequest(settings.mIgnoreOrientationRequest);
+        }
 
         dc.getDisplayRotation().resetAllowAllRotations();
     }
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 4230cd8..cba606c 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -324,7 +324,8 @@
             if (target != imeControlTarget) {
                 // TODO(b/353463205): check if fromUser=false is correct here
                 boolean imeVisible = target.isRequestedVisible(WindowInsets.Type.ime());
-                ImeTracker.Token statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
+                ImeTracker.Token statsToken = ImeTracker.forLogging().onStart(
+                        imeVisible ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE,
                         ImeTracker.ORIGIN_SERVER,
                         imeVisible ? SoftInputShowHideReason.SHOW_INPUT_TARGET_CHANGED
                                 : SoftInputShowHideReason.HIDE_INPUT_TARGET_CHANGED,
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 46312af..b3b8c6e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -156,6 +156,7 @@
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.utils.Slogf;
 import com.android.server.wm.utils.RegionUtils;
+import com.android.window.flags.Flags;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -262,6 +263,7 @@
     int mCurrentUser;
     /** Root task id of the front root task when user switched, indexed by userId. */
     SparseIntArray mUserRootTaskInFront = new SparseIntArray(2);
+    SparseArray<IntArray> mUserVisibleRootTasks = new SparseArray<>();
 
     /**
      * A list of tokens that cause the top activity to be put to sleep.
@@ -1924,7 +1926,18 @@
         // appropriate.
         removeRootTasksInWindowingModes(WINDOWING_MODE_PINNED);
 
-        mUserRootTaskInFront.put(mCurrentUser, focusRootTaskId);
+        if (Flags.enableTopVisibleRootTaskPerUserTracking()) {
+            final IntArray visibleRootTasks = new IntArray();
+            forAllRootTasks(rootTask -> {
+                if (mCurrentUser == rootTask.mUserId && rootTask.isVisibleRequested()) {
+                    visibleRootTasks.add(rootTask.getRootTaskId());
+                }
+            }, /* traverseTopToBottom */ false);
+            mUserVisibleRootTasks.put(mCurrentUser, visibleRootTasks);
+        } else {
+            mUserRootTaskInFront.put(mCurrentUser, focusRootTaskId);
+        }
+
         mCurrentUser = userId;
 
         mTaskSupervisor.mStartingUsers.add(uss);
@@ -1937,22 +1950,60 @@
             Slog.i(TAG, "Persisting top task because it belongs to an always-visible user");
             // For a normal user-switch, we will restore the new user's task. But if the pre-switch
             // top task is an always-visible (Communal) one, keep it even after the switch.
-            mUserRootTaskInFront.put(mCurrentUser, focusRootTaskId);
+            if (Flags.enableTopVisibleRootTaskPerUserTracking()) {
+                final IntArray rootTasks = mUserVisibleRootTasks.get(mCurrentUser);
+                rootTasks.add(focusRootTaskId);
+                mUserVisibleRootTasks.put(mCurrentUser, rootTasks);
+            } else {
+                mUserRootTaskInFront.put(mCurrentUser, focusRootTaskId);
+            }
+
         }
 
         final int restoreRootTaskId = mUserRootTaskInFront.get(userId);
+        final IntArray rootTaskIdsToRestore = mUserVisibleRootTasks.get(userId);
+        boolean homeInFront = false;
+        if (Flags.enableTopVisibleRootTaskPerUserTracking()) {
+            if (rootTaskIdsToRestore == null) {
+                // If there are no root tasks saved, try restore id 0 which should create and launch
+                // the home task.
+                handleRootTaskLaunchOnUserSwitch(/* restoreRootTaskId */INVALID_TASK_ID);
+                homeInFront = true;
+            } else {
+                for (int i = 0; i < rootTaskIdsToRestore.size(); i++) {
+                    handleRootTaskLaunchOnUserSwitch(rootTaskIdsToRestore.get(i));
+                }
+                // Check if the top task is type home
+                if (rootTaskIdsToRestore.size() > 0) {
+                    final int topRootTaskId = rootTaskIdsToRestore.get(
+                            rootTaskIdsToRestore.size() - 1);
+                    homeInFront = isHomeTask(topRootTaskId);
+                }
+            }
+        } else {
+            handleRootTaskLaunchOnUserSwitch(restoreRootTaskId);
+            // Check if the top task is type home
+            homeInFront = isHomeTask(restoreRootTaskId);
+        }
+        return homeInFront;
+    }
+
+    private boolean isHomeTask(int taskId) {
+        final Task rootTask = getRootTask(taskId);
+        return rootTask != null && rootTask.isActivityTypeHome();
+    }
+
+    private void handleRootTaskLaunchOnUserSwitch(int restoreRootTaskId) {
         Task rootTask = getRootTask(restoreRootTaskId);
         if (rootTask == null) {
             rootTask = getDefaultTaskDisplayArea().getOrCreateRootHomeTask();
         }
-        final boolean homeInFront = rootTask.isActivityTypeHome();
         if (rootTask.isOnHomeDisplay()) {
             rootTask.moveToFront("switchUserOnHomeDisplay");
         } else {
             // Root task was moved to another display while user was swapped out.
             resumeHomeActivity(null, "switchUserOnOtherDisplay", getDefaultTaskDisplayArea());
         }
-        return homeInFront;
     }
 
     /** Returns whether the given user is to be always-visible (e.g. a communal profile). */
@@ -1963,7 +2014,11 @@
     }
 
     void removeUser(int userId) {
-        mUserRootTaskInFront.delete(userId);
+        if (Flags.enableTopVisibleRootTaskPerUserTracking()) {
+            mUserVisibleRootTasks.delete(userId);
+        } else {
+            mUserRootTaskInFront.delete(userId);
+        }
     }
 
     /**
@@ -1976,7 +2031,13 @@
                 rootTask = getDefaultTaskDisplayArea().getOrCreateRootHomeTask();
             }
 
-            mUserRootTaskInFront.put(userId, rootTask.getRootTaskId());
+            if (Flags.enableTopVisibleRootTaskPerUserTracking()) {
+                final IntArray rootTasks = mUserVisibleRootTasks.get(userId, new IntArray());
+                rootTasks.add(rootTask.getRootTaskId());
+                mUserVisibleRootTasks.put(userId, rootTasks);
+            } else {
+                mUserRootTaskInFront.put(userId, rootTask.getRootTaskId());
+            }
         }
     }
 
@@ -2124,7 +2185,7 @@
                     if (!tf.isOrganizedTaskFragment()) {
                         return;
                     }
-                    tf.resetAdjacentTaskFragment();
+                    tf.clearAdjacentTaskFragments();
                     tf.setCompanionTaskFragment(null /* companionTaskFragment */);
                     tf.setAnimationParams(TaskFragmentAnimationParams.DEFAULT);
                     if (tf.getTopNonFinishingActivity() != null) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 51b8bd1..9fb5bea 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -94,6 +94,7 @@
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.os.UserHandle;
+import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -239,12 +240,20 @@
     /** This task fragment will be removed when the cleanup of its children are done. */
     private boolean mIsRemovalRequested;
 
-    /** The TaskFragment that is adjacent to this one. */
+    /** @deprecated b/373709676 replace with {@link #mAdjacentTaskFragments} */
+    @Deprecated
     @Nullable
     private TaskFragment mAdjacentTaskFragment;
 
     /**
-     * Unlike the {@link mAdjacentTaskFragment}, the companion TaskFragment is not always visually
+     * The TaskFragments that are adjacent to each other, including this TaskFragment.
+     * All TaskFragments in this set share the same set instance.
+     */
+    @Nullable
+    private AdjacentSet mAdjacentTaskFragments;
+
+    /**
+     * Unlike the {@link #mAdjacentTaskFragments}, the companion TaskFragment is not always visually
      * adjacent to this one, but this TaskFragment will be removed by the organizer if the
      * companion TaskFragment is removed.
      */
@@ -442,15 +451,24 @@
         return service.mWindowOrganizerController.getTaskFragment(token);
     }
 
-    void setAdjacentTaskFragment(@Nullable TaskFragment taskFragment) {
-        if (mAdjacentTaskFragment == taskFragment) {
-            return;
-        }
-        resetAdjacentTaskFragment();
-        if (taskFragment != null) {
+    /** @deprecated b/373709676 replace with {@link #setAdjacentTaskFragments}. */
+    @Deprecated
+    void setAdjacentTaskFragment(@NonNull TaskFragment taskFragment) {
+        if (!Flags.allowMultipleAdjacentTaskFragments()) {
+            if (mAdjacentTaskFragment == taskFragment) {
+                return;
+            }
+            resetAdjacentTaskFragment();
             mAdjacentTaskFragment = taskFragment;
             taskFragment.setAdjacentTaskFragment(this);
+            return;
         }
+
+        setAdjacentTaskFragments(new AdjacentSet(this, taskFragment));
+    }
+
+    void setAdjacentTaskFragments(@NonNull AdjacentSet adjacentTaskFragments) {
+        adjacentTaskFragments.setAsAdjacent();
     }
 
     void setCompanionTaskFragment(@Nullable TaskFragment companionTaskFragment) {
@@ -461,7 +479,14 @@
         return mCompanionTaskFragment;
     }
 
-    void resetAdjacentTaskFragment() {
+    /** @deprecated b/373709676 replace with {@link #clearAdjacentTaskFragments()}. */
+    @Deprecated
+    private void resetAdjacentTaskFragment() {
+        if (Flags.allowMultipleAdjacentTaskFragments()) {
+            throw new IllegalStateException("resetAdjacentTaskFragment shouldn't be called when"
+                    + " allowMultipleAdjacentTaskFragments is enabled. Use either"
+                    + " #clearAdjacentTaskFragments or #removeFromAdjacentTaskFragments");
+        }
         // Reset the adjacent TaskFragment if its adjacent TaskFragment is also this TaskFragment.
         if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) {
             mAdjacentTaskFragment.mAdjacentTaskFragment = null;
@@ -471,6 +496,57 @@
         mDelayLastActivityRemoval = false;
     }
 
+    void clearAdjacentTaskFragments() {
+        if (!Flags.allowMultipleAdjacentTaskFragments()) {
+            resetAdjacentTaskFragment();
+            return;
+        }
+
+        if (mAdjacentTaskFragments != null) {
+            mAdjacentTaskFragments.clear();
+        }
+    }
+
+    void removeFromAdjacentTaskFragments() {
+        if (!Flags.allowMultipleAdjacentTaskFragments()) {
+            resetAdjacentTaskFragment();
+            return;
+        }
+
+        if (mAdjacentTaskFragments != null) {
+            mAdjacentTaskFragments.remove(this);
+        }
+    }
+
+    // TODO(b/373709676): update usages.
+    /** @deprecated b/373709676 replace with {@link #getAdjacentTaskFragments()}. */
+    @Deprecated
+    @Nullable
+    TaskFragment getAdjacentTaskFragment() {
+        return mAdjacentTaskFragment;
+    }
+
+    @Nullable
+    AdjacentSet getAdjacentTaskFragments() {
+        return mAdjacentTaskFragments;
+    }
+
+    boolean hasAdjacentTaskFragment() {
+        if (!Flags.allowMultipleAdjacentTaskFragments()) {
+            return mAdjacentTaskFragment != null;
+        }
+        return mAdjacentTaskFragments != null;
+    }
+
+    boolean isAdjacentTo(@NonNull TaskFragment other) {
+        if (!Flags.allowMultipleAdjacentTaskFragments()) {
+            return mAdjacentTaskFragment == other;
+        }
+        return other != this
+                && mAdjacentTaskFragments != null
+                && mAdjacentTaskFragments.contains(other);
+    }
+
     void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
             @NonNull String processName) {
         mTaskFragmentOrganizer = ITaskFragmentOrganizer.Stub.asInterface(organizer.asBinder());
@@ -566,10 +642,6 @@
         return isEmbedded() && mPinned;
     }
 
-    TaskFragment getAdjacentTaskFragment() {
-        return mAdjacentTaskFragment;
-    }
-
     /** Returns the currently topmost resumed activity. */
     @Nullable
     ActivityRecord getTopResumedActivity() {
@@ -616,7 +688,7 @@
         mResumedActivity = r;
         final ActivityRecord topResumed = mTaskSupervisor.updateTopResumedActivityIfNeeded(reason);
         if (mResumedActivity != null && topResumed != null && topResumed.isEmbedded()
-                && topResumed.getTaskFragment().getAdjacentTaskFragment() == this) {
+                && topResumed.getTaskFragment().isAdjacentTo(this)) {
             // Explicitly updates the last resumed Activity if the resumed activity is
             // adjacent to the top-resumed embedded activity.
             mAtmService.setLastResumedActivityUncheckLocked(mResumedActivity, reason);
@@ -2036,7 +2108,7 @@
     private boolean shouldReportOrientationUnspecified() {
         // Apps and their containers are not allowed to specify orientation from adjacent
         // TaskFragment.
-        return getAdjacentTaskFragment() != null && isVisibleRequested();
+        return hasAdjacentTaskFragment() && isVisibleRequested();
     }
 
     @Override
@@ -3086,7 +3158,7 @@
             EventLogTags.writeWmTfRemoved(System.identityHashCode(this), getTaskId());
         }
         mIsRemovalRequested = false;
-        resetAdjacentTaskFragment();
+        removeFromAdjacentTaskFragments();
         cleanUpEmbeddedTaskFragment();
         final boolean shouldExecuteAppTransition =
                 mClearedTaskFragmentForPip && isTaskVisibleRequested();
@@ -3267,9 +3339,16 @@
             sb.append(" organizerProc=");
             sb.append(mTaskFragmentOrganizerProcessName);
         }
-        if (mAdjacentTaskFragment != null) {
-            sb.append(" adjacent=");
-            sb.append(mAdjacentTaskFragment);
+        if (Flags.allowMultipleAdjacentTaskFragments()) {
+            if (mAdjacentTaskFragments != null) {
+                sb.append(" adjacent=");
+                sb.append(mAdjacentTaskFragments);
+            }
+        } else {
+            if (mAdjacentTaskFragment != null) {
+                sb.append(" adjacent=");
+                sb.append(mAdjacentTaskFragment);
+            }
         }
         sb.append('}');
         return sb.toString();
@@ -3385,4 +3464,94 @@
 
         proto.end(token);
     }
+
+    /** Set of {@link TaskFragment}s that are adjacent to each other. */
+    static class AdjacentSet {
+        private final ArraySet<TaskFragment> mAdjacentSet;
+
+        AdjacentSet(@NonNull TaskFragment... taskFragments) {
+            this(new ArraySet<>(taskFragments));
+        }
+
+        AdjacentSet(@NonNull ArraySet<TaskFragment> taskFragments) {
+            if (!Flags.allowMultipleAdjacentTaskFragments()) {
+                throw new IllegalStateException("allowMultipleAdjacentTaskFragments must be"
+                        + " enabled to set more than two TaskFragments adjacent to each other.");
+            }
+            if (taskFragments.size() < 2) {
+                throw new IllegalArgumentException("Adjacent TaskFragments must contain at least"
+                        + " two TaskFragments, but only " + taskFragments.size()
+                        + " were provided.");
+            }
+            mAdjacentSet = taskFragments;
+        }
+
+        /** Updates each {@link TaskFragment} in the set to be adjacent to each other. */
+        private void setAsAdjacent() {
+            if (mAdjacentSet.isEmpty()
+                    || equals(mAdjacentSet.valueAt(0).mAdjacentTaskFragments)) {
+                // No need to update if any TaskFragment in the set has already been updated to the
+                // same set.
+                return;
+            }
+            for (int i = mAdjacentSet.size() - 1; i >= 0; i--) {
+                final TaskFragment taskFragment = mAdjacentSet.valueAt(i);
+                taskFragment.removeFromAdjacentTaskFragments();
+                taskFragment.mAdjacentTaskFragments = this;
+            }
+        }
+
+        /** Removes the {@link TaskFragment} from the adjacent set. */
+        private void remove(@NonNull TaskFragment taskFragment) {
+            taskFragment.mAdjacentTaskFragments = null;
+            taskFragment.mDelayLastActivityRemoval = false;
+            mAdjacentSet.remove(taskFragment);
+            if (mAdjacentSet.size() < 2) {
+                // To be considered as adjacent, there must be at least 2 TaskFragments in the set.
+                clear();
+            }
+        }
+
+        /** Clears the adjacent relationship. */
+        private void clear() {
+            for (int i = mAdjacentSet.size() - 1; i >= 0; i--) {
+                final TaskFragment taskFragment = mAdjacentSet.valueAt(i);
+                // Clear all reference.
+                taskFragment.mAdjacentTaskFragments = null;
+                taskFragment.mDelayLastActivityRemoval = false;
+            }
+            mAdjacentSet.clear();
+        }
+
+        /** Whether the {@link TaskFragment} is in this adjacent set. */
+        boolean contains(@NonNull TaskFragment taskFragment) {
+            return mAdjacentSet.contains(taskFragment);
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof AdjacentSet other)) {
+                return false;
+            }
+            return mAdjacentSet.equals(other.mAdjacentSet);
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("AdjacentSet{");
+            final int size = mAdjacentSet.size();
+            for (int i = 0; i < size; i++) {
+                if (i != 0) {
+                    sb.append(", ");
+                }
+                sb.append(mAdjacentSet.valueAt(i));
+            }
+            sb.append("}");
+            return sb.toString();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerConstants.java b/services/core/java/com/android/server/wm/WindowManagerConstants.java
index 3ad9b62..9a5c8df 100644
--- a/services/core/java/com/android/server/wm/WindowManagerConstants.java
+++ b/services/core/java/com/android/server/wm/WindowManagerConstants.java
@@ -42,12 +42,23 @@
      * <ul>
      * <li>false: applies to no apps (default)</li>
      * <li>true: applies to all apps</li>
-     * <li>large: applies to all apps but only on large screens</li>
      * </ul>
      */
     private static final String KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST =
             "ignore_activity_orientation_request";
 
+    /**
+     * The orientation of activity will be always "unspecified" except for game apps.
+     * <p>Possible values:
+     * <ul>
+     * <li>none: applies to no apps (default)</li>
+     * <li>all: applies to all apps ({@see #KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST})</li>
+     * <li>large: applies to all apps but only on large screens</li>
+     * </ul>
+     */
+    private static final String KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST_SCREENS =
+            "ignore_activity_orientation_request_screens";
+
     /** The packages that ignore {@link #KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST}. */
     private static final String KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST =
             "opt_out_ignore_activity_orientation_request_list";
@@ -155,6 +166,7 @@
                         updateSystemGestureExclusionLogDebounceMillis();
                         break;
                     case KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST:
+                    case KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST_SCREENS:
                         updateIgnoreActivityOrientationRequest();
                         break;
                     case KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST:
@@ -186,12 +198,16 @@
     }
 
     private void updateIgnoreActivityOrientationRequest() {
-        final String value = mDeviceConfig.getProperty(
+        boolean allScreens = mDeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_WINDOW_MANAGER,
-                KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST);
-        mIgnoreActivityOrientationRequestSmallScreen = Boolean.parseBoolean(value);
-        mIgnoreActivityOrientationRequestLargeScreen = mIgnoreActivityOrientationRequestSmallScreen
-                || ("large".equals(value));
+                KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST, false);
+        String whichScreens = mDeviceConfig.getProperty(
+                DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST_SCREENS);
+        allScreens |= ("all".equalsIgnoreCase(whichScreens));
+        boolean largeScreens = allScreens || ("large".equalsIgnoreCase(whichScreens));
+        mIgnoreActivityOrientationRequestSmallScreen = allScreens;
+        mIgnoreActivityOrientationRequestLargeScreen = largeScreens;
     }
 
     private void updateOptOutIgnoreActivityOrientationRequestList() {
@@ -221,9 +237,9 @@
         pw.print("="); pw.println(mSystemGestureExclusionLimitDp);
         pw.print("  "); pw.print(KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE);
         pw.print("="); pw.println(mSystemGestureExcludedByPreQStickyImmersive);
-        pw.print("  "); pw.print(KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST);
-        pw.print("="); pw.println(mIgnoreActivityOrientationRequestSmallScreen ? "true"
-                : mIgnoreActivityOrientationRequestLargeScreen ? "large" : "false");
+        pw.print("  "); pw.print(KEY_IGNORE_ACTIVITY_ORIENTATION_REQUEST_SCREENS);
+        pw.print("="); pw.println(mIgnoreActivityOrientationRequestSmallScreen ? "all"
+                : mIgnoreActivityOrientationRequestLargeScreen ? "large" : "none");
         if (mOptOutIgnoreActivityOrientationRequestPackages != null) {
             pw.print("  "); pw.print(KEY_OPT_OUT_IGNORE_ACTIVITY_ORIENTATION_REQUEST_LIST);
             pw.print("="); pw.println(mOptOutIgnoreActivityOrientationRequestPackages);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0b6ca75..58319f4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2673,7 +2673,7 @@
 
             if (outRelayoutResult != null) {
                 if (win.syncNextBuffer() && viewVisibility == View.VISIBLE
-                        && win.mSyncSeqId > lastSyncSeqId) {
+                        && win.mSyncSeqId > lastSyncSeqId && !displayContent.mWaitingForConfig) {
                     outRelayoutResult.syncSeqId = win.shouldSyncWithBuffers()
                             ? win.mSyncSeqId
                             : -1;
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 66921ff..1eb8465 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1155,7 +1155,7 @@
                 } else if (!task.mCreatedByOrganizer) {
                     throw new UnsupportedOperationException(
                             "Cannot set non-organized task as adjacent flag root: " + wc);
-                } else if (task.getAdjacentTaskFragment() == null && !clearRoot) {
+                } else if (!task.hasAdjacentTaskFragment() && !clearRoot) {
                     throw new UnsupportedOperationException(
                             "Cannot set non-adjacent task as adjacent flag root: " + wc);
                 }
@@ -1645,9 +1645,15 @@
                             opType, exception);
                     break;
                 }
-                if (taskFragment.getAdjacentTaskFragment() != secondaryTaskFragment) {
+                if (!taskFragment.isAdjacentTo(secondaryTaskFragment)) {
                     // Only have lifecycle effect if the adjacent changed.
-                    taskFragment.setAdjacentTaskFragment(secondaryTaskFragment);
+                    if (Flags.allowMultipleAdjacentTaskFragments()) {
+                        // Activity Embedding only set two TFs adjacent.
+                        taskFragment.setAdjacentTaskFragments(
+                                new TaskFragment.AdjacentSet(taskFragment, secondaryTaskFragment));
+                    } else {
+                        taskFragment.setAdjacentTaskFragment(secondaryTaskFragment);
+                    }
                     effects |= TRANSACT_EFFECTS_LIFECYCLE;
                 }
 
@@ -1663,21 +1669,25 @@
                 break;
             }
             case OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS: {
-                final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
-                if (adjacentTaskFragment == null) {
+                if (!taskFragment.hasAdjacentTaskFragment()) {
                     break;
                 }
-                taskFragment.resetAdjacentTaskFragment();
-                effects |= TRANSACT_EFFECTS_LIFECYCLE;
 
-                // Clear the focused app if the focused app is no longer visible after reset the
-                // adjacent TaskFragments.
+                // Check if the focused app is in the adjacent set that will be cleared.
                 final ActivityRecord focusedApp = taskFragment.getDisplayContent().mFocusedApp;
                 final TaskFragment focusedTaskFragment = focusedApp != null
                         ? focusedApp.getTaskFragment()
                         : null;
-                if ((focusedTaskFragment == taskFragment
-                        || focusedTaskFragment == adjacentTaskFragment)
+                final boolean wasFocusedInAdjacent = focusedTaskFragment == taskFragment
+                        || (focusedTaskFragment != null
+                        && taskFragment.isAdjacentTo(focusedTaskFragment));
+
+                taskFragment.removeFromAdjacentTaskFragments();
+                effects |= TRANSACT_EFFECTS_LIFECYCLE;
+
+                // Clear the focused app if the focused app is no longer visible after reset the
+                // adjacent TaskFragments.
+                if (wasFocusedInAdjacent
                         && !focusedTaskFragment.shouldBeVisible(null /* starting */)) {
                     focusedTaskFragment.getDisplayContent().setFocusedApp(null /* newFocus */);
                 }
@@ -2207,10 +2217,15 @@
             throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by"
                     + " organizer root1=" + root1 + " root2=" + root2);
         }
-        if (root1.getAdjacentTaskFragment() == root2) {
+        if (root1.isAdjacentTo(root2)) {
             return TRANSACT_EFFECTS_NONE;
         }
-        root1.setAdjacentTaskFragment(root2);
+        if (Flags.allowMultipleAdjacentTaskFragments()) {
+            // TODO(b/373709676): allow three roots.
+            root1.setAdjacentTaskFragments(new TaskFragment.AdjacentSet(root1, root2));
+        } else {
+            root1.setAdjacentTaskFragment(root2);
+        }
         return TRANSACT_EFFECTS_LIFECYCLE;
     }
 
@@ -2225,10 +2240,10 @@
             throw new IllegalArgumentException("clearAdjacentRootsHierarchyOp: Not created by"
                     + " organizer root=" + root);
         }
-        if (root.getAdjacentTaskFragment() == null) {
+        if (!root.hasAdjacentTaskFragment()) {
             return TRANSACT_EFFECTS_NONE;
         }
-        root.resetAdjacentTaskFragment();
+        root.removeFromAdjacentTaskFragments();
         return TRANSACT_EFFECTS_LIFECYCLE;
     }
 
diff --git a/services/core/jni/com_android_server_am_Freezer.cpp b/services/core/jni/com_android_server_am_Freezer.cpp
index 8148728..e9a99f0 100644
--- a/services/core/jni/com_android_server_am_Freezer.cpp
+++ b/services/core/jni/com_android_server_am_Freezer.cpp
@@ -68,7 +68,7 @@
 
 bool isFreezerSupported(JNIEnv *env, jclass) {
     std::string path;
-    if (!getAttributePathForTask("FreezerState", getpid(), &path)) {
+    if (!CgroupGetAttributePathForTask("FreezerState", getpid(), &path)) {
         ALOGI("No attribute for FreezerState");
         return false;
     }
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 0c9a89b..8533eaf 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -114,7 +114,7 @@
     /** Creates intent that is ot be invoked to cancel an in-progress UI session. */
     public Intent createCancelIntent(IBinder requestId, String packageName) {
         return IntentFactory.createCancelUiIntent(mContext, requestId,
-                /*shouldShowCancellationUi=*/ true, packageName);
+                /*shouldShowCancellationUi=*/ true, packageName, mUserId);
     }
 
     /**
@@ -177,7 +177,7 @@
 
         IntentCreationResult intentCreationResult = IntentFactory
                 .createCredentialSelectorIntentForCredMan(mContext, requestInfo, providerDataList,
-                        new ArrayList<>(disabledProviderDataList), mResultReceiver);
+                        new ArrayList<>(disabledProviderDataList), mResultReceiver, mUserId);
         requestSessionMetric.collectUiConfigurationResults(
                 mContext, intentCreationResult, mUserId);
         Intent intent = intentCreationResult.getIntent();
@@ -211,7 +211,7 @@
             RequestSessionMetric requestSessionMetric) {
         IntentCreationResult intentCreationResult = IntentFactory
                 .createCredentialSelectorIntentForAutofill(mContext, requestInfo, new ArrayList<>(),
-                        mResultReceiver);
+                        mResultReceiver, mUserId);
         requestSessionMetric.collectUiConfigurationResults(
                 mContext, intentCreationResult, mUserId);
         return intentCreationResult.getIntent();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 91f1aaf..8ca3919 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -2471,6 +2471,26 @@
                 eq(false));
     }
 
+    @Test
+    public void onDisplayChange_canceledAfterStop() {
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+
+        // stop the dpc (turn it down)
+        mHolder.dpc.stop();
+        advanceTime(1);
+
+        // To trigger all the changes that can happen, we will completely change the underlying
+        // display device.
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+
+        // Call onDisplayChange after we stopped DPC and make sure it doesn't crash
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        advanceTime(1);
+
+        // No crash = success
+    }
+
     /**
      * Creates a mock and registers it to {@link LocalServices}.
      */
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java
index 874e991..495e853 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java
@@ -46,7 +46,6 @@
 import android.os.test.TestLooper;
 import android.service.dreams.IDreamService;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -108,10 +107,8 @@
                 .thenReturn(Context.ACTIVITY_TASK_SERVICE);
 
         final PowerManager powerManager = new PowerManager(mContext, mPowerManager, null, null);
-        when(mContext.getSystemService(Context.POWER_SERVICE))
+        when(mContext.getSystemService(PowerManager.class))
                 .thenReturn(powerManager);
-        when(mContext.getSystemServiceName(PowerManager.class))
-                .thenReturn(Context.POWER_SERVICE);
         when(mContext.getResources()).thenReturn(mResources);
 
         mToken = new Binder();
@@ -234,8 +231,13 @@
     }
 
     @Test
-    @FlakyTest(bugId = 293109503)
     public void serviceDisconnect_resetsScreenTimeout() throws RemoteException {
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.config_resetScreenTimeoutOnUnexpectedDreamExit))
+                .thenReturn(true);
+        // Recreate DreamManager because the configuration gets retrieved in the constructor
+        mDreamController = new DreamController(mContext, mHandler, mListener);
+
         // Start dream.
         mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
                 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
@@ -254,8 +256,13 @@
     }
 
     @Test
-    @FlakyTest(bugId = 293109503)
     public void binderDied_resetsScreenTimeout() throws RemoteException {
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.config_resetScreenTimeoutOnUnexpectedDreamExit))
+                .thenReturn(true);
+        // Recreate DreamManager because the configuration gets retrieved in the constructor
+        mDreamController = new DreamController(mContext, mHandler, mListener);
+
         // Start dream.
         mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/,
                 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
index 0304a74..cd8d415 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/InstallDependencyHelperTest.java
@@ -21,11 +21,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.pm.SharedLibraryInfo;
@@ -90,32 +86,15 @@
     }
 
     @Test
-    public void testResolveLibraryDependenciesIfNeeded_errorInSharedLibrariesImpl()
-            throws Exception {
-        doThrow(new PackageManagerException(new Exception("xyz")))
-                .when(mSharedLibraries).collectMissingSharedLibraryInfos(any());
-
-        PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
-        CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
-        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
-                0, mHandler, callback);
-        callback.assertFailure();
-
-        assertThat(callback.error).hasMessageThat().contains("xyz");
-    }
-
-    @Test
     public void testResolveLibraryDependenciesIfNeeded_failsToBind() throws Exception {
         // Return a non-empty list as missing dependency
         PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
         List<SharedLibraryInfo> missingDependency = Collections.singletonList(
                 mock(SharedLibraryInfo.class));
-        when(mSharedLibraries.collectMissingSharedLibraryInfos(eq(pkg)))
-                .thenReturn(missingDependency);
 
         CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ false);
-        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
-                0, mHandler, callback);
+        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(missingDependency, pkg,
+                mComputer, 0, mHandler, callback);
         callback.assertFailure();
 
         assertThat(callback.error).hasMessageThat().contains(
@@ -128,12 +107,10 @@
         // Return an empty list as missing dependency
         PackageLite pkg = getPackageLite(TEST_APP_USING_SDK1_AND_SDK2);
         List<SharedLibraryInfo> missingDependency = Collections.emptyList();
-        when(mSharedLibraries.collectMissingSharedLibraryInfos(eq(pkg)))
-                .thenReturn(missingDependency);
 
         CallbackHelper callback = new CallbackHelper(/*expectSuccess=*/ true);
-        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(pkg, mComputer,
-                0, mHandler, callback);
+        mInstallDependencyHelper.resolveLibraryDependenciesIfNeeded(missingDependency, pkg,
+                mComputer, 0, mHandler, callback);
         callback.assertSuccess();
     }
 
diff --git a/services/tests/wmtests/res/xml/bookmarks.xml b/services/tests/wmtests/res/xml/bookmarks.xml
index 3fc7c76..cbbbc73 100644
--- a/services/tests/wmtests/res/xml/bookmarks.xml
+++ b/services/tests/wmtests/res/xml/bookmarks.xml
@@ -48,7 +48,7 @@
 
     <bookmark
         category="android.intent.category.APP_CONTACTS"
-        androidprv:keycode="KEYCODE_C"
+        androidprv:keycode="KEYCODE_P"
         androidprv:modifierState="META|SHIFT" />
 
     <bookmark
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index d4a921c..1b0d9dc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -554,6 +554,7 @@
 
     @Test
     public void testSetRequestedOrientationUpdatesConfiguration() throws Exception {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
                 .setConfigChanges(ORIENTATION_CONFIG_CHANGES)
@@ -641,6 +642,7 @@
 
     @Test
     public void ignoreRequestedOrientationForResizableInSplitWindows() {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final ActivityRecord activity = createActivityWith2LevelTask();
         final Task task = activity.getTask();
         final Task rootTask = activity.getRootTask();
@@ -685,6 +687,7 @@
 
     @Test
     public void respectRequestedOrientationForNonResizableInSplitWindows() {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea();
         spyOn(tda);
         doReturn(true).when(tda).supportsNonResizableMultiWindow();
@@ -1906,6 +1909,7 @@
 
     @Test
     public void testActivityOnCancelFixedRotationTransform() {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final ActivityRecord activity = createActivityWithTask();
         final DisplayRotation displayRotation = activity.mDisplayContent.getDisplayRotation();
         final RemoteDisplayChangeController remoteDisplayChangeController = activity
@@ -2054,6 +2058,7 @@
 
     @Test
     public void testFixedRotationSnapshotStartingWindow() {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final ActivityRecord activity = createActivityWithTask();
         // TaskSnapshotSurface requires a fullscreen opaque window.
         final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
@@ -2278,6 +2283,7 @@
     @Test
     public void testSupportsFreeform() {
         final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setComponent(getUniqueComponentName(mContext.getPackageName()))
                 .setCreateTask(true)
                 .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE)
                 .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
@@ -2410,6 +2416,7 @@
 
     @Test
     public void testOrientationForScreenOrientationBehind() {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final Task task = createTask(mDisplayContent);
         // Activity below
         new ActivityBuilder(mAtm)
@@ -2507,6 +2514,7 @@
     @SetupWindows(addWindows = W_ACTIVITY)
     @Test
     public void testLandscapeSeascapeRotationByApp() {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final Task task = new TaskBuilder(mSupervisor)
                 .setDisplay(mDisplayContent).setCreateActivity(true).build();
         final ActivityRecord activity = task.getTopNonFinishingActivity();
@@ -2572,6 +2580,7 @@
     @Test
     @Presubmit
     public void testGetOrientation() {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         // ActivityBuilder will resume top activities and cause the activity been added into
         // opening apps list. Since this test is focus on the effect of visible on getting
         // orientation, we skip app transition to avoid interference.
@@ -2663,8 +2672,8 @@
     @Test
     public void testSetOrientation_restrictedByTargetSdk() {
         mSetFlagsRule.enableFlags(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT);
-        mDisplayContent.setIgnoreOrientationRequest(true);
         makeDisplayLargeScreen(mDisplayContent);
+        assertTrue(mDisplayContent.getIgnoreOrientationRequest());
 
         assertSetOrientation(Build.VERSION_CODES.CUR_DEVELOPMENT, CATEGORY_SOCIAL, false);
         assertSetOrientation(Build.VERSION_CODES.CUR_DEVELOPMENT, CATEGORY_GAME, true);
@@ -2702,6 +2711,7 @@
 
     @Test
     public void testRespectTopFullscreenOrientation() {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
         final Configuration displayConfig = activity.mDisplayContent.getConfiguration();
         final Configuration activityConfig = activity.getConfiguration();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index e0b29c9..1cb1e3c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -511,6 +511,7 @@
     @Test
     public void testSupportsMultiWindow_nonResizable() {
         final ActivityRecord activity = new ActivityBuilder(mAtm)
+                .setComponent(getUniqueComponentName(mContext.getPackageName()))
                 .setCreateTask(true)
                 .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
                 .build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 40da9ea..579ed66 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -365,8 +365,8 @@
         assertTrue(outPrevActivities.isEmpty());
         assertTrue(predictable);
         // reset
-        tf1.setAdjacentTaskFragment(null);
-        tf2.setAdjacentTaskFragment(null);
+        tf1.clearAdjacentTaskFragments();
+        tf2.clearAdjacentTaskFragments();
         tf1.setCompanionTaskFragment(null);
         tf2.setCompanionTaskFragment(null);
 
@@ -398,8 +398,8 @@
         assertTrue(predictable);
         // reset
         outPrevActivities.clear();
-        tf2.setAdjacentTaskFragment(null);
-        tf3.setAdjacentTaskFragment(null);
+        tf2.clearAdjacentTaskFragments();
+        tf3.clearAdjacentTaskFragments();
 
         final TaskFragment tf4 = createTaskFragmentWithActivity(task);
         // Stacked + next companion to top => predict for previous activity below companion.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java
index 87dbca5..060b379 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaGroupTest.java
@@ -84,6 +84,7 @@
 
     @Test
     public void testGetRequestedOrientationForDisplay() {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final Task task = new TaskBuilder(mSupervisor)
                 .setTaskDisplayArea(mTaskDisplayArea).setCreateActivity(true).build();
         final ActivityRecord activity = task.getTopNonFinishingActivity();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index 7b2cd63..0a7df5a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -491,6 +491,7 @@
     @Test
     public void testSetIgnoreOrientationRequest_callSuperOnDescendantOrientationChangedNoSensor() {
         final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea();
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final Task stack =
                 new TaskBuilder(mSupervisor).setOnTop(!ON_TOP).setCreateActivity(true).build();
         final ActivityRecord activity = stack.getTopNonFinishingActivity();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 9cbea2e..db71f2b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -898,6 +898,7 @@
     public void testOrientationDefinedByKeyguard() {
         final DisplayContent dc = mDisplayContent;
         dc.getDisplayPolicy().setAwake(true);
+        dc.setIgnoreOrientationRequest(false);
 
         // Create a window that requests landscape orientation. It will define device orientation
         // by default.
@@ -925,6 +926,7 @@
     @Test
     public void testOrientationForAspectRatio() {
         final DisplayContent dc = createNewDisplay();
+        dc.setIgnoreOrientationRequest(false);
 
         // When display content is created its configuration is not yet initialized, which could
         // cause unnecessary configuration propagation, so initialize it here.
@@ -1034,6 +1036,7 @@
     @Test
     public void testAllowsTopmostFullscreenOrientation() {
         final DisplayContent dc = createNewDisplay();
+        dc.setIgnoreOrientationRequest(false);
         assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, dc.getOrientation());
         dc.getDisplayRotation().setFixedToUserRotation(
                 IWindowManager.FIXED_TO_USER_ROTATION_DISABLED);
@@ -1112,6 +1115,7 @@
     @Test
     public void testOnDescendantOrientationRequestChanged() {
         final DisplayContent dc = createNewDisplay();
+        dc.setIgnoreOrientationRequest(false);
         dc.getDisplayRotation().setFixedToUserRotation(
                 IWindowManager.FIXED_TO_USER_ROTATION_DISABLED);
         dc.getDefaultTaskDisplayArea().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -1130,6 +1134,7 @@
     @Test
     public void testOnDescendantOrientationRequestChanged_FrozenToUserRotation() {
         final DisplayContent dc = createNewDisplay();
+        dc.setIgnoreOrientationRequest(false);
         dc.getDisplayRotation().setFixedToUserRotation(
                 IWindowManager.FIXED_TO_USER_ROTATION_ENABLED);
         dc.getDisplayRotation().setUserRotation(
@@ -1152,6 +1157,7 @@
     @Test
     public void testOrientationBehind() {
         assertNull(mDisplayContent.getLastOrientationSource());
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final ActivityRecord prev = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setScreenOrientation(getRotatedOrientation(mDisplayContent)).build();
         prev.setVisibleRequested(false);
@@ -1172,6 +1178,7 @@
     @Test
     public void testFixedToUserRotationChanged() {
         final DisplayContent dc = createNewDisplay();
+        dc.setIgnoreOrientationRequest(false);
         dc.getDisplayRotation().setFixedToUserRotation(
                 IWindowManager.FIXED_TO_USER_ROTATION_ENABLED);
         dc.getDisplayRotation().setUserRotation(
@@ -1589,6 +1596,7 @@
             W_INPUT_METHOD, W_NOTIFICATION_SHADE })
     @Test
     public void testApplyTopFixedRotationTransform() {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
         spyOn(displayPolicy);
         // Only non-movable (gesture) navigation bar will be animated by fixed rotation animation.
@@ -1742,6 +1750,7 @@
     @Test
     public void testFixedRotationWithPip() {
         final DisplayContent displayContent = mDefaultDisplay;
+        displayContent.setIgnoreOrientationRequest(false);
         unblockDisplayRotation(displayContent);
         // Unblock the condition in PinnedTaskController#continueOrientationChangeIfNeeded.
         doNothing().when(displayContent).prepareAppTransition(anyInt());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
index 6397334..6527af1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationImmersiveAppCompatPolicyTests.java
@@ -77,7 +77,7 @@
         when(mMockActivityRecord.findMainWindow()).thenReturn(mMockWindowState);
 
         doReturn(mMockActivityRecord).when(mDisplayContent).topRunningActivity();
-        when(mDisplayContent.getIgnoreOrientationRequest()).thenReturn(true);
+        mDisplayContent.setIgnoreOrientationRequest(true);
 
         mMockAppCompatConfiguration = mock(AppCompatConfiguration.class);
         when(mMockAppCompatConfiguration.isDisplayRotationImmersiveAppCompatPolicyEnabled())
@@ -195,7 +195,7 @@
 
     @Test
     public void testIsRotationLockEnforced_ignoreOrientationRequestDisabled_lockNotEnforced() {
-        when(mDisplayContent.getIgnoreOrientationRequest()).thenReturn(false);
+        mDisplayContent.setIgnoreOrientationRequest(false);
 
         assertIsRotationLockEnforcedReturnsFalseForAllRotations();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index 708d686..bd15bc4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -106,6 +106,8 @@
 
         // Display: 1920x1200 (landscape). First and second display are both 860x1200 (portrait).
         mDisplay = new DualDisplayContent.Builder(mAtm, 1920, 1200).build();
+        // The test verifies that the display area can affect display's getLastOrientation().
+        mDisplay.setIgnoreOrientationRequest(false);
         mFirstRoot = mDisplay.mFirstRoot;
         mSecondRoot = mDisplay.mSecondRoot;
         mFirstTda = mDisplay.getTaskDisplayArea(FEATURE_FIRST_TASK_CONTAINER);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 7cb62c5..d965125 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -132,6 +132,7 @@
 
     @Test
     public void testClosingAppDifferentTaskOrientation() {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
@@ -146,6 +147,7 @@
 
     @Test
     public void testMoveTaskToBackDifferentTaskOrientation() {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
 
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 7e8bd38..699ed02 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -41,6 +41,7 @@
 import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -63,6 +64,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
 
 import android.app.ActivityOptions;
 import android.app.WindowConfiguration;
@@ -77,12 +79,14 @@
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.util.Pair;
 
 import androidx.test.filters.MediumTest;
 
 import com.android.internal.app.ResolverActivity;
+import com.android.window.flags.Flags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -693,6 +697,7 @@
     @Test
     public void testAwakeFromSleepingWithAppConfiguration() {
         final DisplayContent display = mRootWindowContainer.getDefaultDisplay();
+        display.setIgnoreOrientationRequest(false);
         final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
         activity.moveFocusableActivityToTop("test");
         assertTrue(activity.getRootTask().isFocusedRootTaskOnDisplay());
@@ -1331,6 +1336,38 @@
         assertEquals(taskDisplayArea.getTopRootTask(), taskDisplayArea.getRootHomeTask());
     }
 
+    @EnableFlags(Flags.FLAG_ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING)
+    @Test
+    public void testSwitchUser_withVisibleRootTasks_storesAllVisibleRootTasksForCurrentUser() {
+        // Set up root tasks
+        final Task rootTask1 = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final Task rootTask2 = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
+                WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        final Task rootTask3 = mRootWindowContainer.getDefaultTaskDisplayArea().createRootTask(
+                WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+        doReturn(rootTask3).when(mRootWindowContainer).getTopDisplayFocusedRootTask();
+
+        // Set up user ids and visibility
+        rootTask1.mUserId = mRootWindowContainer.mCurrentUser;
+        rootTask2.mUserId = mRootWindowContainer.mCurrentUser;
+        rootTask3.mUserId = mRootWindowContainer.mCurrentUser;
+        rootTask1.mVisibleRequested = false;
+        rootTask2.mVisibleRequested = true;
+        rootTask3.mVisibleRequested = true;
+
+        // Switch to a different user
+        int currentUser = mRootWindowContainer.mCurrentUser;
+        int otherUser = currentUser + 1;
+        mRootWindowContainer.switchUser(otherUser, null);
+
+        // Verify that the previous user persists it's previous visible root tasks
+        assertArrayEquals(
+                new int[]{rootTask2.mTaskId, rootTask3.mTaskId},
+                mRootWindowContainer.mUserVisibleRootTasks.get(currentUser).toArray()
+        );
+    }
+
     @Test
     public void testLockAllProfileTasks() {
         final int profileUid = UserHandle.PER_USER_RANGE + UserHandle.MIN_SECONDARY_USER_ID;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index bf96f0eb..201ff51 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -107,6 +107,8 @@
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
+import android.view.DisplayCutout;
+import android.view.DisplayInfo;
 import android.view.InsetsFrameProvider;
 import android.view.InsetsSource;
 import android.view.InsetsState;
@@ -210,6 +212,37 @@
         return setUpApp(builder.build(), appBuilder);
     }
 
+    private void setUpLargeScreenDisplayWithApp(int dw, int dh) {
+        final DisplayContent display = mDisplayContent;
+        final DisplayInfo displayInfo = display.getDisplayInfo();
+        displayInfo.logicalWidth = dw;
+        displayInfo.logicalHeight = dh;
+        // Prevent legacy sdk from being affected by INSETS_DECOUPLED_CONFIGURATION_ENFORCED.
+        display.mInitialDisplayCutout = displayInfo.displayCutout = DisplayCutout.NO_CUTOUT;
+        // Smallest screen width=747dp according to 1400/(300/160).
+        display.mBaseDisplayDensity = displayInfo.logicalDensityDpi =
+                TestDisplayContent.DEFAULT_LOGICAL_DISPLAY_DENSITY;
+        doNothing().when(display).updateDisplayInfo(any());
+        resizeDisplay(display, displayInfo.logicalWidth, displayInfo.logicalHeight);
+        assertTrue(display.isLargeScreen());
+        if (com.android.window.flags.Flags.universalResizableByDefault()) {
+            assertTrue("Large screen must ignore orientation request",
+                    display.getIgnoreOrientationRequest());
+        } else {
+            display.setIgnoreOrientationRequest(true);
+        }
+        setUpApp(display, null /* appBuilder */);
+        spyOn(display.getDisplayRotation());
+    }
+
+    private void setUpLandscapeLargeScreenDisplayWithApp() {
+        setUpLargeScreenDisplayWithApp(/* dw */ 2800, /* dh */ 1400);
+    }
+
+    private void setUpPortraitLargeScreenDisplayWithApp() {
+        setUpLargeScreenDisplayWithApp(/* dw */ 1400, /* dh */ 2800);
+    }
+
     @Test
     public void testHorizontalReachabilityEnabledForTranslucentActivities() {
         testReachabilityEnabledForTranslucentActivity(/* dw */ 2500,  /* dh */1000,
@@ -658,9 +691,7 @@
 
     @Test
     public void testIsLetterboxed_activityFromBubble_returnsFalse() {
-        setUpDisplaySizeWithApp(1000, 2500);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-        spyOn(mActivity);
+        setUpPortraitLargeScreenDisplayWithApp();
         doReturn(true).when(mActivity).getLaunchedFromBubble();
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
 
@@ -1694,10 +1725,9 @@
     @Test
     public void testGetLetterboxInnerBounds_noScalingApplied() {
         // Set up a display in portrait and ignoring orientation request.
-        final int dw = 1400;
-        final int dh = 2800;
-        setUpDisplaySizeWithApp(dw, dh);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpPortraitLargeScreenDisplayWithApp();
+        final int dw = mDisplayContent.mBaseDisplayWidth;
+        final int dh = mDisplayContent.mBaseDisplayHeight;
 
         // Rotate display to landscape.
         rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
@@ -1823,8 +1853,7 @@
     @Test
     public void testDisplayIgnoreOrientationRequest_fixedOrientationAppLaunchedLetterbox() {
         // Set up a display in landscape and ignoring orientation request.
-        setUpDisplaySizeWithApp(2800, 1400);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
 
         // Portrait fixed app without max aspect.
         prepareUnresizable(mActivity, /* maxAspect= */ 0, SCREEN_ORIENTATION_PORTRAIT);
@@ -1852,8 +1881,7 @@
     @Test
     public void testDisplayIgnoreOrientationRequest_fixedOrientationAppRespectMinAspectRatio() {
         // Set up a display in landscape and ignoring orientation request.
-        setUpDisplaySizeWithApp(2800, 1400);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
 
         // Portrait fixed app with min aspect ratio higher that aspect ratio override for fixed
         // orientation letterbox.
@@ -1884,8 +1912,7 @@
     @Test
     public void testDisplayIgnoreOrientationRequest_fixedOrientationAppRespectMaxAspectRatio() {
         // Set up a display in landscape and ignoring orientation request.
-        setUpDisplaySizeWithApp(2800, 1400);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
 
         // Portrait fixed app with max aspect ratio lower that aspect ratio override for fixed
         // orientation letterbox.
@@ -1915,8 +1942,7 @@
     @Test
     public void testDisplayIgnoreOrientationRequest_fixedOrientationAppWithAspectRatioOverride() {
         // Set up a display in landscape and ignoring orientation request.
-        setUpDisplaySizeWithApp(2800, 1400);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
 
         final float fixedOrientationLetterboxAspectRatio = 1.1f;
         mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(
@@ -2011,8 +2037,7 @@
     @Test
     public void testDisplayIgnoreOrientationRequest_unresizableWithCorrespondingMinAspectRatio() {
         // Set up a display in landscape and ignoring orientation request.
-        setUpDisplaySizeWithApp(2800, 1400);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
 
         final float fixedOrientationLetterboxAspectRatio = 1.1f;
         mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(
@@ -2045,13 +2070,10 @@
     @Test
     public void testComputeConfigResourceOverrides_unresizableApp() {
         // Set up a display in landscape and ignoring orientation request.
-        setUpDisplaySizeWithApp(2800, 1400);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
 
         prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
 
-        final Rect activityBounds = new Rect(mActivity.getBounds());
-
         int originalScreenWidthDp = mActivity.getConfiguration().screenWidthDp;
         int originalScreenHeighthDp = mActivity.getConfiguration().screenHeightDp;
 
@@ -2068,7 +2090,7 @@
 
         // After we rotate, the activity should go in the size-compat mode and report the same
         // configuration values.
-        assertDownScaled();
+        assertThat(mActivity.inSizeCompatMode()).isTrue();
         assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
         assertEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp);
         assertEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp);
@@ -2087,14 +2109,11 @@
     @Test
     public void testComputeConfigResourceOverrides_resizableFixedOrientationActivity() {
         // Set up a display in landscape and ignoring orientation request.
-        setUpDisplaySizeWithApp(2800, 1400);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
 
         // Portrait fixed app without max aspect.
         prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, false /* isUnresizable */);
 
-        final Rect activityBounds = new Rect(mActivity.getBounds());
-
         int originalScreenWidthDp = mActivity.getConfiguration().screenWidthDp;
         int originalScreenHeighthDp = mActivity.getConfiguration().screenHeightDp;
 
@@ -2206,10 +2225,10 @@
 
     @Test
     public void testSystemFullscreenOverrideForLandscapeDisplay() {
-        final int displayWidth = 1600;
-        final int displayHeight = 1400;
-        setUpDisplaySizeWithApp(displayWidth, displayHeight);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
+        final int displayWidth = mDisplayContent.mBaseDisplayWidth;
+        final int displayHeight = mDisplayContent.mBaseDisplayHeight;
+
         spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
         doReturn(true).when(
                 mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
@@ -2226,10 +2245,10 @@
 
     @Test
     public void testSystemFullscreenOverrideForPortraitDisplay() {
-        final int displayWidth = 1400;
-        final int displayHeight = 1600;
-        setUpDisplaySizeWithApp(displayWidth, displayHeight);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpPortraitLargeScreenDisplayWithApp();
+        final int displayWidth = mDisplayContent.mBaseDisplayWidth;
+        final int displayHeight = mDisplayContent.mBaseDisplayHeight;
+
         spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
         doReturn(true).when(
                 mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
@@ -2619,7 +2638,7 @@
     @EnableCompatChanges({ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION})
     public void testOverrideRespectRequestedOrientationIsEnabled_orientationIsRespected() {
         // Set up a display in landscape
-        setUpDisplaySizeWithApp(2800, 1400);
+        setUpDisplaySizeWithApp(1000, 500);
 
         final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false,
                 RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT);
@@ -2636,7 +2655,7 @@
     @EnableCompatChanges({ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION})
     public void testOverrideRespectRequestedOrientationIsEnabled_multiWindow_orientationIgnored() {
         // Set up a display in landscape
-        setUpDisplaySizeWithApp(2800, 1400);
+        setUpDisplaySizeWithApp(1000, 500);
 
         final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false,
                 RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT);
@@ -2655,10 +2674,9 @@
     @Test
     public void testSplitAspectRatioForUnresizableLandscapeApps() {
         // Set up a display in portrait and ignoring orientation request.
-        int screenWidth = 1400;
-        int screenHeight = 1600;
-        setUpDisplaySizeWithApp(screenWidth, screenHeight);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLargeScreenDisplayWithApp(1400, 2400);
+        final int screenWidth = mDisplayContent.mBaseDisplayWidth;
+        final int screenHeight = mDisplayContent.mBaseDisplayHeight;
         mActivity.mWmService.mAppCompatConfiguration
                 .setIsSplitScreenAspectRatioForUnresizableAppsEnabled(true);
 
@@ -2692,10 +2710,9 @@
     @Test
     public void testDisplayAspectRatioForResizablePortraitApps() {
         // Set up a display in portrait and ignoring orientation request.
-        int displayWidth = 1400;
-        int displayHeight = 1600;
-        setUpDisplaySizeWithApp(displayWidth, displayHeight);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLargeScreenDisplayWithApp(1400, 2400);
+        final int displayWidth = mDisplayContent.mBaseDisplayWidth;
+        final int displayHeight = mDisplayContent.mBaseDisplayHeight;
         mWm.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(2f);
 
         // Enable display aspect ratio to take precedence before
@@ -2728,10 +2745,9 @@
     @Test
     public void testDisplayAspectRatioForResizableLandscapeApps() {
         // Set up a display in landscape and ignoring orientation request.
-        int displayWidth = 1600;
-        int displayHeight = 1400;
-        setUpDisplaySizeWithApp(displayWidth, displayHeight);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
+        final int displayWidth = mDisplayContent.mBaseDisplayWidth;
+        final int displayHeight = mDisplayContent.mBaseDisplayHeight;
         mWm.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(2f);
 
         // Enable display aspect ratio to take precedence before
@@ -2764,10 +2780,9 @@
     @Test
     public void testDisplayAspectRatioForUnresizableLandscapeApps() {
         // Set up a display in portrait and ignoring orientation request.
-        int displayWidth = 1400;
-        int displayHeight = 1600;
-        setUpDisplaySizeWithApp(displayWidth, displayHeight);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpPortraitLargeScreenDisplayWithApp();
+        final int displayWidth = mDisplayContent.mBaseDisplayWidth;
+        final int displayHeight = mDisplayContent.mBaseDisplayHeight;
 
         mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f);
         // Enable display aspect ratio to take precedence before
@@ -2791,10 +2806,9 @@
     @Test
     public void testDisplayAspectRatioForUnresizablePortraitApps() {
         // Set up a display in landscape and ignoring orientation request.
-        int displayWidth = 1600;
-        int displayHeight = 1400;
-        setUpDisplaySizeWithApp(displayWidth, displayHeight);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
+        final int displayWidth = mDisplayContent.mBaseDisplayWidth;
+        final int displayHeight = mDisplayContent.mBaseDisplayHeight;
 
         mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f);
         // Enable display aspect ratio to take precedence before
@@ -2819,8 +2833,7 @@
     public void
             testDisplayIgnoreOrientationRequest_orientationLetterboxBecameSizeCompatAfterRotate() {
         // Set up a display in landscape and ignoring orientation request.
-        setUpDisplaySizeWithApp(2800, 1400);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
 
         // Portrait fixed app without max aspect.
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
@@ -2837,7 +2850,7 @@
         // App should be in size compat.
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertDownScaled();
+        assertThat(mActivity.inSizeCompatMode()).isTrue();
         assertEquals(activityBounds.width(), newActivityBounds.width());
         assertEquals(activityBounds.height(), newActivityBounds.height());
         assertActivityMaxBoundsSandboxed();
@@ -2846,8 +2859,7 @@
     @Test
     public void testDisplayIgnoreOrientationRequest_sizeCompatAfterRotate() {
         // Set up a display in portrait and ignoring orientation request.
-        setUpDisplaySizeWithApp(1400, 2800);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpPortraitLargeScreenDisplayWithApp();
 
         // Portrait fixed app without max aspect.
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
@@ -2882,9 +2894,8 @@
     @Test
     public void testDisplayIgnoreOrientationRequest_newLaunchedOrientationAppInLetterbox() {
         // Set up a display in landscape and ignoring orientation request.
-        setUpDisplaySizeWithApp(2800, 1400);
+        setUpLandscapeLargeScreenDisplayWithApp();
         final DisplayContent display = mActivity.mDisplayContent;
-        display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
 
         // Portrait fixed app without max aspect.
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
@@ -2928,9 +2939,7 @@
     @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION})
     public void testDisplayIgnoreOrientationRequest_orientationChangedToUnspecified() {
         // Set up a display in landscape and ignoring orientation request.
-        setUpDisplaySizeWithApp(2800, 1400);
-        final DisplayContent display = mActivity.mDisplayContent;
-        display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
 
         // Portrait fixed app without max aspect.
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
@@ -2950,9 +2959,8 @@
     @Test
     public void testDisplayIgnoreOrientationRequest_newLaunchedMaxAspectApp() {
         // Set up a display in landscape and ignoring orientation request.
-        setUpDisplaySizeWithApp(2800, 1400);
+        setUpLandscapeLargeScreenDisplayWithApp();
         final DisplayContent display = mActivity.mDisplayContent;
-        display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
 
         // Portrait fixed app without max aspect.
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
@@ -3001,9 +3009,7 @@
     @SuppressWarnings("GuardedBy")
     public void testDisplayIgnoreOrientationRequest_pausedAppNotLostSizeCompat() {
         // Set up a display in landscape and ignoring orientation request.
-        setUpDisplaySizeWithApp(2800, 1400);
-        final DisplayContent display = mActivity.mDisplayContent;
-        display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
 
         // Portrait fixed app.
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
@@ -3019,7 +3025,6 @@
         // App should be in size compat.
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertDownScaled();
         assertThat(mActivity.inSizeCompatMode()).isTrue();
         // Activity max bounds are sandboxed due to size compat mode.
         assertActivityMaxBoundsSandboxed();
@@ -3035,7 +3040,7 @@
         verify(scmPolicy, never()).clearSizeCompatMode();
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertDownScaled();
+        assertThat(mActivity.inSizeCompatMode()).isTrue();
         assertEquals(activityBounds, mActivity.getBounds());
         // Activity max bounds are sandboxed due to size compat.
         assertActivityMaxBoundsSandboxed();
@@ -3044,9 +3049,8 @@
     @Test
     public void testDisplayIgnoreOrientationRequest_rotated180_notInSizeCompat() {
         // Set up a display in landscape and ignoring orientation request.
-        setUpDisplaySizeWithApp(2800, 1400);
+        setUpLandscapeLargeScreenDisplayWithApp();
         final DisplayContent display = mActivity.mDisplayContent;
-        display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
 
         // Portrait fixed app.
         prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
@@ -3063,7 +3067,7 @@
         // App should be in size compat.
         assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy()
                 .isLetterboxedForFixedOrientationAndAspectRatio());
-        assertDownScaled();
+        assertThat(mActivity.inSizeCompatMode()).isTrue();
         assertActivityMaxBoundsSandboxed();
 
         // Rotate display to landscape.
@@ -3246,8 +3250,9 @@
 
     @Test
     public void testTaskDisplayAreaNotFillDisplay() {
-        setUpDisplaySizeWithApp(1400, 2800);
+        setUpPortraitLargeScreenDisplayWithApp();
         final DisplayContent display = mActivity.mDisplayContent;
+        display.setIgnoreOrientationRequest(false);
         final TaskDisplayArea taskDisplayArea = mActivity.getDisplayArea();
         taskDisplayArea.setBounds(0, 0, 1000, 2400);
 
@@ -3430,8 +3435,7 @@
     @Test
     public void testIsHorizontalReachabilityEnabled_splitScreen_false() {
         mAtm.mDevEnableNonResizableMultiWindow = true;
-        setUpDisplaySizeWithApp(2800, 1000);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
         mWm.mAppCompatConfiguration.setIsHorizontalReachabilityEnabled(true);
         setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);
         final TestSplitOrganizer organizer =
@@ -3518,8 +3522,7 @@
 
     @Test
     public void testIsHorizontalReachabilityEnabled_emptyBounds_true() {
-        setUpDisplaySizeWithApp(/* dw */ 2800, /* dh */ 1000);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
         mWm.mAppCompatConfiguration.setIsHorizontalReachabilityEnabled(true);
         setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);
 
@@ -3566,8 +3569,7 @@
 
     @Test
     public void testIsHorizontalReachabilityEnabled_doesNotMatchParentHeight_false() {
-        setUpDisplaySizeWithApp(2800, 1000);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
         mWm.mAppCompatConfiguration.setIsHorizontalReachabilityEnabled(true);
         setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);
 
@@ -3787,12 +3789,10 @@
     }
 
     private void assertLandscapeActivityAlignedToBottomWithNavbar(boolean immersive) {
-        final int screenHeight = 2800;
-        final int screenWidth = 1400;
+        setUpPortraitLargeScreenDisplayWithApp();
+        final int screenHeight = mDisplayContent.mBaseDisplayHeight;
+        final int screenWidth = mDisplayContent.mBaseDisplayWidth;
         final int taskbarHeight = 200;
-        setUpDisplaySizeWithApp(screenWidth, screenHeight);
-
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true);
         mActivity.mWmService.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(1.0f);
 
         final InsetsSource navSource = new InsetsSource(
@@ -3972,8 +3972,7 @@
             float letterboxHorizontalPositionMultiplier, Rect fixedOrientationLetterbox,
             Rect sizeCompatUnscaled, Rect sizeCompatScaled) {
         // Set up a display in landscape and ignoring orientation request.
-        setUpDisplaySizeWithApp(2800, 1400);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
 
         mActivity.mWmService.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(
                 letterboxHorizontalPositionMultiplier);
@@ -4177,13 +4176,28 @@
 
     @Test
     public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() {
+        // Set up a display in landscape and ignoring orientation request.
+        setUpLandscapeLargeScreenDisplayWithApp();
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+        final Consumer<Float> assertHorizontalPosition = letterboxHorizontalPositionMultiplier -> {
+            mActivity.mWmService.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(
+                    letterboxHorizontalPositionMultiplier);
+            mActivity.recomputeConfiguration();
+            assertFitted();
+            // Rotate to put activity in size compat mode.
+            rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+            assertTrue(mActivity.inSizeCompatMode());
+            // Activity is in size compat mode but not scaled.
+            assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds());
+            if (letterboxHorizontalPositionMultiplier < 1f) {
+                rotateDisplay(mActivity.mDisplayContent, ROTATION_0);
+            }
+        };
         // When activity width equals parent width, multiplier shouldn't have any effect.
-        assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
-                /* letterboxHorizontalPositionMultiplier */ 0.0f);
-        assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
-                /* letterboxHorizontalPositionMultiplier */ 0.5f);
-        assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
-                /* letterboxHorizontalPositionMultiplier */ 1.0f);
+        assertHorizontalPosition.accept(0.0f);
+        assertHorizontalPosition.accept(0.5f);
+        assertHorizontalPosition.accept(1.0f);
     }
 
     @Test
@@ -4354,8 +4368,7 @@
     @Test
     public void testUpdateResolvedBoundsHorizontalPosition_bookModeEnabled() {
         // Set up a display in landscape with a fixed-orientation PORTRAIT app
-        setUpDisplaySizeWithApp(2800, 1400);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
         mWm.mAppCompatConfiguration.setIsAutomaticReachabilityInBookModeEnabled(true);
         mWm.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(
                 1.0f /*letterboxHorizontalPositionMultiplier*/);
@@ -4380,8 +4393,7 @@
     @Test
     public void testUpdateResolvedBoundsHorizontalPosition_bookModeDisabled_centered() {
         // Set up a display in landscape with a fixed-orientation PORTRAIT app
-        setUpDisplaySizeWithApp(2800, 1400);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpLandscapeLargeScreenDisplayWithApp();
         mWm.mAppCompatConfiguration.setIsAutomaticReachabilityInBookModeEnabled(false);
         mWm.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f);
         prepareUnresizable(mActivity, 1.75f, SCREEN_ORIENTATION_PORTRAIT);
@@ -4462,8 +4474,7 @@
             float letterboxVerticalPositionMultiplier, Rect fixedOrientationLetterbox,
             Rect sizeCompatUnscaled, Rect sizeCompatScaled) {
         // Set up a display in portrait and ignoring orientation request.
-        setUpDisplaySizeWithApp(1400, 2800);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        setUpPortraitLargeScreenDisplayWithApp();
 
         mActivity.mWmService.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(
                 letterboxVerticalPositionMultiplier);
@@ -5036,23 +5047,6 @@
         return (dimensionToSplit - (dividerWindowWidth - dividerInsets * 2)) / 2;
     }
 
-    private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
-            float letterboxHorizontalPositionMultiplier) {
-        // Set up a display in landscape and ignoring orientation request.
-        setUpDisplaySizeWithApp(2800, 1400);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-
-        mActivity.mWmService.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(
-                letterboxHorizontalPositionMultiplier);
-        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
-        assertFitted();
-        // Rotate to put activity in size compat mode.
-        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
-        assertTrue(mActivity.inSizeCompatMode());
-        // Activity is in size compat mode but not scaled.
-        assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds());
-    }
-
     private void assertVerticalPositionForDifferentDisplayConfigsForPortraitActivity(
             float letterboxVerticalPositionMultiplier) {
         // Set up a display in portrait and ignoring orientation request.
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 08622e6..921228f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -78,6 +78,8 @@
 
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.protolog.ProtoLog;
+import com.android.internal.protolog.WmProtoLogGroups;
 import com.android.server.AnimationThread;
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
@@ -183,6 +185,8 @@
     }
 
     private void setUp() {
+        ProtoLog.init(WmProtoLogGroups.values());
+
         if (mOnBeforeServicesCreated != null) {
             mOnBeforeServicesCreated.run();
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 65a6a69..dafa96f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -51,6 +51,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.clearInvocations;
@@ -62,6 +63,7 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.Binder;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.annotations.Presubmit;
 import android.view.SurfaceControl;
 import android.view.View;
@@ -1066,6 +1068,98 @@
                 Math.min(outConfig.screenWidthDp, outConfig.screenHeightDp));
     }
 
+    @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
+    @Test
+    public void testSetAdjacentTaskFragments() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment tf0 = createTaskFragmentWithActivity(task);
+        final TaskFragment tf1 = createTaskFragmentWithActivity(task);
+        final TaskFragment tf2 = createTaskFragmentWithActivity(task);
+        final TaskFragment.AdjacentSet adjacentTfs = new TaskFragment.AdjacentSet(tf0, tf1, tf2);
+        assertFalse(tf0.hasAdjacentTaskFragment());
+
+        tf0.setAdjacentTaskFragments(adjacentTfs);
+
+        assertSame(adjacentTfs, tf0.getAdjacentTaskFragments());
+        assertSame(adjacentTfs, tf1.getAdjacentTaskFragments());
+        assertSame(adjacentTfs, tf2.getAdjacentTaskFragments());
+        assertTrue(tf0.hasAdjacentTaskFragment());
+        assertTrue(tf1.hasAdjacentTaskFragment());
+        assertTrue(tf2.hasAdjacentTaskFragment());
+
+        final TaskFragment.AdjacentSet adjacentTfs2 = new TaskFragment.AdjacentSet(tf0, tf1);
+        tf0.setAdjacentTaskFragments(adjacentTfs2);
+
+        assertSame(adjacentTfs2, tf0.getAdjacentTaskFragments());
+        assertSame(adjacentTfs2, tf1.getAdjacentTaskFragments());
+        assertNull(tf2.getAdjacentTaskFragments());
+        assertTrue(tf0.hasAdjacentTaskFragment());
+        assertTrue(tf1.hasAdjacentTaskFragment());
+        assertFalse(tf2.hasAdjacentTaskFragment());
+    }
+
+    @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
+    @Test
+    public void testClearAdjacentTaskFragments() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment tf0 = createTaskFragmentWithActivity(task);
+        final TaskFragment tf1 = createTaskFragmentWithActivity(task);
+        final TaskFragment tf2 = createTaskFragmentWithActivity(task);
+        final TaskFragment.AdjacentSet adjacentTfs = new TaskFragment.AdjacentSet(tf0, tf1, tf2);
+        tf0.setAdjacentTaskFragments(adjacentTfs);
+
+        tf0.clearAdjacentTaskFragments();
+
+        assertNull(tf0.getAdjacentTaskFragments());
+        assertNull(tf1.getAdjacentTaskFragments());
+        assertNull(tf2.getAdjacentTaskFragments());
+        assertFalse(tf0.hasAdjacentTaskFragment());
+        assertFalse(tf1.hasAdjacentTaskFragment());
+        assertFalse(tf2.hasAdjacentTaskFragment());
+    }
+
+    @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
+    @Test
+    public void testRemoveFromAdjacentTaskFragments() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment tf0 = createTaskFragmentWithActivity(task);
+        final TaskFragment tf1 = createTaskFragmentWithActivity(task);
+        final TaskFragment tf2 = createTaskFragmentWithActivity(task);
+        final TaskFragment.AdjacentSet adjacentTfs = new TaskFragment.AdjacentSet(tf0, tf1, tf2);
+        tf0.setAdjacentTaskFragments(adjacentTfs);
+
+        tf0.removeFromAdjacentTaskFragments();
+
+        assertNull(tf0.getAdjacentTaskFragments());
+        assertSame(adjacentTfs, tf1.getAdjacentTaskFragments());
+        assertSame(adjacentTfs, tf2.getAdjacentTaskFragments());
+        assertFalse(adjacentTfs.contains(tf0));
+        assertTrue(tf1.isAdjacentTo(tf2));
+        assertTrue(tf2.isAdjacentTo(tf1));
+        assertFalse(tf1.isAdjacentTo(tf0));
+        assertFalse(tf0.isAdjacentTo(tf1));
+        assertFalse(tf0.isAdjacentTo(tf0));
+        assertFalse(tf1.isAdjacentTo(tf1));
+    }
+
+    @EnableFlags(Flags.FLAG_ALLOW_MULTIPLE_ADJACENT_TASK_FRAGMENTS)
+    @Test
+    public void testRemoveFromAdjacentTaskFragmentsWhenRemove() {
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment tf0 = createTaskFragmentWithActivity(task);
+        final TaskFragment tf1 = createTaskFragmentWithActivity(task);
+        final TaskFragment tf2 = createTaskFragmentWithActivity(task);
+        final TaskFragment.AdjacentSet adjacentTfs = new TaskFragment.AdjacentSet(tf0, tf1, tf2);
+        tf0.setAdjacentTaskFragments(adjacentTfs);
+
+        tf0.removeImmediately();
+
+        assertNull(tf0.getAdjacentTaskFragments());
+        assertSame(adjacentTfs, tf1.getAdjacentTaskFragments());
+        assertSame(adjacentTfs, tf2.getAdjacentTaskFragments());
+        assertFalse(adjacentTfs.contains(tf0));
+    }
+
     private WindowState createAppWindow(ActivityRecord app, String name) {
         final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, app, name,
                 0 /* ownerId */, false /* ownerCanAddInternalSystemWindow */, new TestIWindow());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index e4512c3..1febc9f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -527,6 +527,7 @@
 
     @Test
     public void testHandlesOrientationChangeFromDescendant() {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final Task rootTask = createTask(mDisplayContent,
                 WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
         final Task leafTask1 = createTaskInRootTask(rootTask, 0 /* userId */);
@@ -1570,6 +1571,7 @@
 
     @Test
     public void testNotSpecifyOrientationByFloatingTask() {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final Task task = new TaskBuilder(mSupervisor)
                 .setCreateActivity(true).setCreateParentTask(true).build();
         final ActivityRecord activity = task.getTopMostActivity();
@@ -1589,6 +1591,7 @@
 
     @Test
     public void testNotSpecifyOrientation_taskDisplayAreaNotFocused() {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final TaskDisplayArea firstTaskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
         final TaskDisplayArea secondTaskDisplayArea = createTaskDisplayArea(
                 mDisplayContent, mRootWindowContainer.mWmService, "TestTaskDisplayArea",
@@ -1625,6 +1628,7 @@
 
     @Test
     public void testTaskOrientationOnDisplayWindowingModeChange() {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         // Skip unnecessary operations to speed up the test.
         mAtm.deferWindowLayout();
         final Task task = getTestTask();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 039a3dd..78f32c1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1333,6 +1333,7 @@
 
     @Test
     public void testDeferRotationForTransientLaunch() {
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final TestTransitionPlayer player = registerTestTransitionPlayer();
         assumeFalse(mDisplayContent.mTransitionController.useShellTransitionsRotation());
         final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
index 42752c3..f1180ff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
@@ -314,6 +314,7 @@
         runTestScenario((robot) -> {
             robot.transparentActivity((ta) -> {
                 ta.applyOnActivity((a) -> {
+                    a.setIgnoreOrientationRequest(false);
                     a.applyToTopActivity((topActivity) -> {
                         topActivity.mWmService.mAppCompatConfiguration
                                 .setLetterboxHorizontalPositionMultiplier(1.0f);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 410fa28..bb1e3ea 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -777,6 +777,7 @@
     @Test
     public void testSetIgnoreOrientationRequest_taskDisplayArea() {
         removeGlobalMinSizeRestriction();
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
         final Task rootTask = taskDisplayArea.createRootTask(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
@@ -815,6 +816,7 @@
     @Test
     public void testSetIgnoreOrientationRequest_displayContent() {
         removeGlobalMinSizeRestriction();
+        mDisplayContent.setIgnoreOrientationRequest(false);
         final TaskDisplayArea taskDisplayArea = mDisplayContent.getDefaultTaskDisplayArea();
         final Task rootTask = taskDisplayArea.createRootTask(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 3a97cc6..b27025c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -201,14 +201,10 @@
      * {@link WindowTestsBase#setUpBase()}.
      */
     private static boolean sGlobalOverridesChecked;
+
     /**
      * Whether device-specific overrides have already been checked in
-     * {@link WindowTestsBase#setUpBase()} when the default display is used.
-     */
-    private static boolean sOverridesCheckedDefaultDisplay;
-    /**
-     * Whether device-specific overrides have already been checked in
-     * {@link WindowTestsBase#setUpBase()} when a {@link TestDisplayContent} is used.
+     * {@link WindowTestsBase#setUpBase()}.
      */
     private static boolean sOverridesCheckedTestDisplay;
 
@@ -332,17 +328,14 @@
     private void checkDeviceSpecificOverridesNotApplied() {
         // Check global overrides
         if (!sGlobalOverridesChecked) {
+            sGlobalOverridesChecked = true;
             assertEquals(0, mWm.mAppCompatConfiguration.getFixedOrientationLetterboxAspectRatio(),
                     0 /* delta */);
-            sGlobalOverridesChecked = true;
         }
         // Check display-specific overrides
-        if (!sOverridesCheckedDefaultDisplay && mDisplayContent == mDefaultDisplay) {
-            assertFalse(mDisplayContent.getIgnoreOrientationRequest());
-            sOverridesCheckedDefaultDisplay = true;
-        } else if (!sOverridesCheckedTestDisplay && mDisplayContent instanceof TestDisplayContent) {
-            assertFalse(mDisplayContent.getIgnoreOrientationRequest());
+        if (!sOverridesCheckedTestDisplay) {
             sOverridesCheckedTestDisplay = true;
+            assertFalse(mDisplayContent.mHasSetIgnoreOrientationRequest);
         }
     }
 
@@ -1121,7 +1114,7 @@
         displayContent.getDisplayRotation().configure(width, height);
         final Configuration c = new Configuration();
         displayContent.computeScreenConfiguration(c);
-        displayContent.onRequestedOverrideConfigurationChanged(c);
+        displayContent.performDisplayOverrideConfigUpdate(c);
     }
 
     static void makeDisplayLargeScreen(DisplayContent displayContent) {
diff --git a/telephony/java/android/telephony/CellIdentityCdma.java b/telephony/java/android/telephony/CellIdentityCdma.java
index 4e5a246..4cb622b 100644
--- a/telephony/java/android/telephony/CellIdentityCdma.java
+++ b/telephony/java/android/telephony/CellIdentityCdma.java
@@ -105,20 +105,30 @@
      */
     public CellIdentityCdma(int nid, int sid, int bid, int lon, int lat,
             @Nullable String alphal, @Nullable String alphas) {
-        super(TAG, CellInfo.TYPE_CDMA, null, null, alphal, alphas);
-        mNetworkId = inRangeOrUnavailable(nid, 0, NETWORK_ID_MAX);
-        mSystemId = inRangeOrUnavailable(sid, 0, SYSTEM_ID_MAX);
-        mBasestationId = inRangeOrUnavailable(bid, 0, BASESTATION_ID_MAX);
-        lat = inRangeOrUnavailable(lat, LATITUDE_MIN, LATITUDE_MAX);
-        lon = inRangeOrUnavailable(lon, LONGITUDE_MIN, LONGITUDE_MAX);
-
-        if (!isNullIsland(lat, lon)) {
-            mLongitude = lon;
-            mLatitude = lat;
+        super(TAG, CellInfo.TYPE_CDMA, null, null, Flags.cleanupCdma() ? null : alphal,
+                Flags.cleanupCdma() ? null : alphas);
+        if (Flags.cleanupCdma()) {
+            mNetworkId = CellInfo.UNAVAILABLE;
+            mSystemId = CellInfo.UNAVAILABLE;
+            mBasestationId = CellInfo.UNAVAILABLE;
+            mLongitude = CellInfo.UNAVAILABLE;
+            mLatitude = CellInfo.UNAVAILABLE;
+            mGlobalCellId = null;
         } else {
-            mLongitude = mLatitude = CellInfo.UNAVAILABLE;
+            mNetworkId = inRangeOrUnavailable(nid, 0, NETWORK_ID_MAX);
+            mSystemId = inRangeOrUnavailable(sid, 0, SYSTEM_ID_MAX);
+            mBasestationId = inRangeOrUnavailable(bid, 0, BASESTATION_ID_MAX);
+            lat = inRangeOrUnavailable(lat, LATITUDE_MIN, LATITUDE_MAX);
+            lon = inRangeOrUnavailable(lon, LONGITUDE_MIN, LONGITUDE_MAX);
+
+            if (!isNullIsland(lat, lon)) {
+                mLongitude = lon;
+                mLatitude = lat;
+            } else {
+                mLongitude = mLatitude = CellInfo.UNAVAILABLE;
+            }
+            updateGlobalCellId();
         }
-        updateGlobalCellId();
     }
 
     private CellIdentityCdma(@NonNull CellIdentityCdma cid) {
@@ -300,14 +310,34 @@
     /** Construct from Parcel, type has already been processed */
     private CellIdentityCdma(Parcel in) {
         super(TAG, CellInfo.TYPE_CDMA, in);
-        mNetworkId = in.readInt();
-        mSystemId = in.readInt();
-        mBasestationId = in.readInt();
-        mLongitude = in.readInt();
-        mLatitude = in.readInt();
 
-        updateGlobalCellId();
-        if (DBG) log(toString());
+        if (Flags.cleanupCdma()) {
+            in.readInt();
+            mNetworkId = CellInfo.UNAVAILABLE;
+
+            in.readInt();
+            mSystemId = CellInfo.UNAVAILABLE;
+
+            in.readInt();
+            mBasestationId = CellInfo.UNAVAILABLE;
+
+            in.readInt();
+            mLongitude = CellInfo.UNAVAILABLE;
+
+            in.readInt();
+            mLatitude = CellInfo.UNAVAILABLE;
+
+            mGlobalCellId = null;
+        } else {
+            mNetworkId = in.readInt();
+            mSystemId = in.readInt();
+            mBasestationId = in.readInt();
+            mLongitude = in.readInt();
+            mLatitude = in.readInt();
+
+            updateGlobalCellId();
+            if (DBG) log(toString());
+        }
     }
 
     /**
diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java
index 5298e67..12a7294 100644
--- a/telephony/java/android/telephony/CellSignalStrengthCdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java
@@ -21,6 +21,7 @@
 import android.os.Parcelable;
 import android.os.PersistableBundle;
 
+import com.android.internal.telephony.flags.Flags;
 import com.android.telephony.Rlog;
 
 import java.util.Objects;
@@ -68,13 +69,17 @@
      */
     public CellSignalStrengthCdma(int cdmaDbm, int cdmaEcio, int evdoDbm, int evdoEcio,
             int evdoSnr) {
-        mCdmaDbm = inRangeOrUnavailable(cdmaDbm, -120, 0);
-        mCdmaEcio = inRangeOrUnavailable(cdmaEcio, -160, 0);
-        mEvdoDbm = inRangeOrUnavailable(evdoDbm, -120, 0);
-        mEvdoEcio = inRangeOrUnavailable(evdoEcio, -160, 0);
-        mEvdoSnr = inRangeOrUnavailable(evdoSnr, 0, 8);
+        if (Flags.cleanupCdma()) {
+            setDefaultValues();
+        } else {
+            mCdmaDbm = inRangeOrUnavailable(cdmaDbm, -120, 0);
+            mCdmaEcio = inRangeOrUnavailable(cdmaEcio, -160, 0);
+            mEvdoDbm = inRangeOrUnavailable(evdoDbm, -120, 0);
+            mEvdoEcio = inRangeOrUnavailable(evdoEcio, -160, 0);
+            mEvdoSnr = inRangeOrUnavailable(evdoSnr, 0, 8);
 
-        updateLevel(null, null);
+            updateLevel(null, null);
+        }
     }
 
     /** @hide */
@@ -84,6 +89,10 @@
 
     /** @hide */
     protected void copyFrom(CellSignalStrengthCdma s) {
+        if (Flags.cleanupCdma()) {
+            setDefaultValues();
+            return;
+        }
         mCdmaDbm = s.mCdmaDbm;
         mCdmaEcio = s.mCdmaEcio;
         mEvdoDbm = s.mEvdoDbm;
@@ -389,6 +398,7 @@
     /** @hide */
     @Override
     public boolean isValid() {
+        if (Flags.cleanupCdma()) return false;
         return !this.equals(sInvalid);
     }
 
@@ -446,7 +456,12 @@
         mEvdoEcio = in.readInt();
         mEvdoSnr = in.readInt();
         mLevel = in.readInt();
-        if (DBG) log("CellSignalStrengthCdma(Parcel): " + toString());
+
+        if (Flags.cleanupCdma()) {
+            setDefaultValues();
+        } else {
+            if (DBG) log("CellSignalStrengthCdma(Parcel): " + toString());
+        }
     }
 
     /** Implement the Parcelable interface */
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 3f8e066..9694b90 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6387,6 +6387,7 @@
      * @deprecated use {@link #getImsPrivateUserIdentity()}
      */
     @UnsupportedAppUsage
+    @Deprecated
     public String getIsimImpi() {
         try {
             IPhoneSubInfo info = getSubscriberInfoService();
@@ -6476,6 +6477,7 @@
      * @deprecated use {@link #getImsPublicUserIdentities()}
      */
     @UnsupportedAppUsage
+    @Deprecated
     @Nullable
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public String[] getIsimImpu() {
@@ -8510,7 +8512,9 @@
             if (telephony == null) {
                 throw new IllegalStateException("telephony service is null.");
             }
-            telephony.rebootModem(getSlotIndex());
+            if (!telephony.rebootModem(getSlotIndex())) {
+                throw new RuntimeException("Couldn't reboot modem (it may be not supported)");
+            }
         } catch (RemoteException ex) {
             Rlog.e(TAG, "rebootRadio RemoteException", ex);
             throw ex.rethrowAsRuntimeException();
@@ -14051,6 +14055,7 @@
      * @hide
      */
     @SystemApi
+    @Deprecated
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CARRIERLOCK)
     public int setAllowedCarriers(int slotIndex, List<CarrierIdentifier> carriers) {
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index eaeed2a..b1a5b1b 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -17,6 +17,7 @@
 package android.telephony.satellite.stub;
 
 import android.annotation.NonNull;
+import android.hardware.radio.network.IRadioNetwork;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.telephony.IBooleanConsumer;
@@ -586,7 +587,11 @@
      *   SatelliteResult:SATELLITE_RESULT_NO_RESOURCES
      *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
      *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *
+     * @deprecated Use
+     * {@link IRadioNetwork#setSatellitePlmn(int, int, String[], String[])}.
      */
+    @Deprecated
     public void setSatellitePlmn(@NonNull int simLogicalSlotIndex,
             @NonNull List<String> carrierPlmnList, @NonNull List<String> allSatellitePlmnList,
             @NonNull IIntegerConsumer resultCallback) {
@@ -608,7 +613,11 @@
      *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
      *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
      *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *
+     * @deprecated Use
+     * {@link IRadioNetwork#setSatelliteEnabledForCarrier(int, int, boolean)}.
      */
+    @Deprecated
     public void setSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex,
             @NonNull boolean satelliteEnabled, @NonNull IIntegerConsumer callback) {
         // stub implementation
@@ -629,7 +638,11 @@
      *   SatelliteResult:SATELLITE_RESULT_MODEM_ERROR
      *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
      *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     *
+     * @deprecated Use
+     * {@link IRadioNetwork#isSatelliteEnabledForCarrier(int, int)}.
      */
+    @Deprecated
     public void requestIsSatelliteEnabledForCarrier(@NonNull int simLogicalSlotIndex,
             @NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) {
         // stub implementation