Merge "Adding required annotations to mark SystemApi" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index c33c053..664dfe9 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8777,6 +8777,11 @@
   @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager {
     method @Deprecated @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
     method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+    method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+    method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
+    field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
+    field public static final int APP_FUNCTION_STATE_DISABLED = 2; // 0x2
+    field public static final int APP_FUNCTION_STATE_ENABLED = 1; // 0x1
   }
 
   @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service {
@@ -8818,6 +8823,7 @@
     field public static final String PROPERTY_RETURN_VALUE = "returnValue";
     field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
     field public static final int RESULT_DENIED = 1; // 0x1
+    field public static final int RESULT_DISABLED = 6; // 0x6
     field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
     field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
     field public static final int RESULT_OK = 0; // 0x0
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index b781ce5..f21c3e8 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -493,6 +493,9 @@
          * {@link Environment#getExternalStoragePublicDirectory(String)} with
          * {@link Environment#DIRECTORY_DOWNLOADS}).
          *
+         * All non-visible downloads that are not modified in the last 7 days will be deleted during
+         * idle runs.
+         *
          * @param uri a file {@link Uri} indicating the destination for the downloaded file.
          * @return this object
          */
@@ -796,7 +799,9 @@
          * public Downloads directory (as returned by
          * {@link Environment#getExternalStoragePublicDirectory(String)} with
          * {@link Environment#DIRECTORY_DOWNLOADS}) will be visible in system's Downloads UI
-         * and the rest will not be visible.
+         * and the rest will not be visible. All non-visible downloads that are not modified
+         * in the last 7 days will be deleted during idle runs.
+         *
          * (e.g. {@link Context#getExternalFilesDir(String)}) will not be visible.
          */
         @Deprecated
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a39f216..c21fe0e 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1619,22 +1619,6 @@
     public static final String EXTRA_DECLINE_COLOR = "android.declineColor";
 
     /**
-     * {@link #extras} key: {@link Icon} of an image used as an overlay Icon on
-     * {@link Notification#mLargeIcon} for {@link EnRouteStyle} notifications.
-     * This extra is an {@code Icon}.
-     * @hide
-     */
-    @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
-    public static final String EXTRA_ENROUTE_OVERLAY_ICON = "android.enrouteOverlayIcon";
-
-    /**
-     * {@link #extras} key: text used as a sub-text for the largeIcon of
-     * {@link EnRouteStyle} notification. This extra is a {@code CharSequence}.
-     * @hide
-     */
-    @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
-    public static final String EXTRA_ENROUTE_LARGE_ICON_SUBTEXT = "android.enrouteLargeIconSubText";
-    /**
      * {@link #extras} key: whether the notification should be colorized as
      * supplied to {@link Builder#setColorized(boolean)}.
      */
@@ -3152,7 +3136,6 @@
         }
 
         if (Flags.apiRichOngoing()) {
-            visitIconUri(visitor, extras.getParcelable(EXTRA_ENROUTE_OVERLAY_ICON, Icon.class));
             visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_TRACKER_ICON, Icon.class));
             visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_START_ICON, Icon.class));
             visitIconUri(visitor, extras.getParcelable(EXTRA_PROGRESS_END_ICON, Icon.class));
@@ -11173,145 +11156,6 @@
     }
 
     /**
-     * TODO(b/360827871): Make EnRouteStyle public.
-     * A style used to represent the progress of a real-world journey with a known destination.
-     * For example:
-     * <ul>
-     *     <li>Delivery tracking</li>
-     *     <li>Ride progress</li>
-     *     <li>Flight tracking</li>
-     * </ul>
-     *
-     * The exact fields from {@link Notification} that are shown with this style may vary by
-     * the surface where this update appears, but the following fields are recommended:
-     * <ul>
-     *     <li>{@link Notification.Builder#setContentTitle}</li>
-     *     <li>{@link Notification.Builder#setContentText}</li>
-     *     <li>{@link Notification.Builder#setSubText}</li>
-     *     <li>{@link Notification.Builder#setLargeIcon}</li>
-     *     <li>{@link Notification.Builder#setProgress}</li>
-     *     <li>{@link Notification.Builder#setWhen} - This should be the future time of the next,
-     *     final, or most important stop on this journey.</li>
-     * </ul>
-     * @hide
-     */
-    @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
-    public static class EnRouteStyle extends Notification.Style {
-
-        @Nullable
-        private Icon mOverlayIcon = null;
-
-        @Nullable
-        private CharSequence mLargeIconSubText = null;
-
-        public EnRouteStyle() {
-        }
-
-        /**
-         * Returns the overlay icon to be displayed on {@link Notification#mLargeIcon}.
-         * @see EnRouteStyle#setOverlayIcon
-         */
-        @Nullable
-        public Icon getOverlayIcon() {
-            return mOverlayIcon;
-        }
-
-        /**
-         * Optional icon to be displayed on {@link Notification#mLargeIcon}.
-         *
-         * This image will be cropped to a circle and will obscure
-         * a semicircle of the right side of the large icon.
-         */
-        @NonNull
-        public EnRouteStyle setOverlayIcon(@Nullable Icon overlayIcon) {
-            mOverlayIcon = overlayIcon;
-            return this;
-        }
-
-        /**
-         * Returns the sub-text for {@link Notification#mLargeIcon}.
-         * @see EnRouteStyle#setLargeIconSubText
-         */
-        @Nullable
-        public CharSequence getLargeIconSubText() {
-            return mLargeIconSubText;
-        }
-
-        /**
-         * Optional text which generally related to
-         * the {@link Notification.Builder#setLargeIcon} or {@link #setOverlayIcon} or both.
-         */
-        @NonNull
-        public EnRouteStyle setLargeIconSubText(@Nullable CharSequence largeIconSubText) {
-            mLargeIconSubText = stripStyling(largeIconSubText);
-            return this;
-        }
-
-         /**
-         * @hide
-         */
-        @Override
-        public boolean areNotificationsVisiblyDifferent(Style other) {
-            if (other == null || getClass() != other.getClass()) {
-                return true;
-            }
-
-            final EnRouteStyle enRouteStyle = (EnRouteStyle) other;
-            return !Objects.equals(mOverlayIcon, enRouteStyle.mOverlayIcon)
-                    || !Objects.equals(mLargeIconSubText, enRouteStyle.mLargeIconSubText);
-        }
-
-        /**
-         * @hide
-         */
-        @Override
-        public void addExtras(Bundle extras) {
-            super.addExtras(extras);
-            extras.putParcelable(EXTRA_ENROUTE_OVERLAY_ICON, mOverlayIcon);
-            extras.putCharSequence(EXTRA_ENROUTE_LARGE_ICON_SUBTEXT, mLargeIconSubText);
-        }
-
-        /**
-         * @hide
-         */
-        @Override
-        protected void restoreFromExtras(Bundle extras) {
-            super.restoreFromExtras(extras);
-            mOverlayIcon = extras.getParcelable(EXTRA_ENROUTE_OVERLAY_ICON, Icon.class);
-            mLargeIconSubText = extras.getCharSequence(EXTRA_ENROUTE_LARGE_ICON_SUBTEXT);
-        }
-
-        /**
-         * @hide
-         */
-        @Override
-        public void purgeResources() {
-            super.purgeResources();
-            if (mOverlayIcon != null) {
-                mOverlayIcon.convertToAshmem();
-            }
-        }
-
-        /**
-         * @hide
-         */
-        @Override
-        public void reduceImageSizes(Context context) {
-            super.reduceImageSizes(context);
-            if (mOverlayIcon != null) {
-                final Resources resources = context.getResources();
-                final boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
-
-                int rightIconSize = resources.getDimensionPixelSize(isLowRam
-                        ? R.dimen.notification_right_icon_size_low_ram
-                        : R.dimen.notification_right_icon_size);
-                mOverlayIcon.scaleDownIfNecessary(rightIconSize, rightIconSize);
-            }
-        }
-    }
-
-
-    /**
      * A Notification Style used to to define a notification whose expanded state includes
      * a highly customizable progress bar with segments, steps, a custom tracker icon,
      * and custom icons at the start and end of the progress bar.
diff --git a/core/java/android/app/admin/PolicySizeVerifier.java b/core/java/android/app/admin/PolicySizeVerifier.java
index 7f8e50e..1e03e1f 100644
--- a/core/java/android/app/admin/PolicySizeVerifier.java
+++ b/core/java/android/app/admin/PolicySizeVerifier.java
@@ -22,7 +22,9 @@
 import android.os.PersistableBundle;
 
 import com.android.internal.util.Preconditions;
+import com.android.modules.utils.ModifiedUtf8;
 
+import java.io.UTFDataFormatException;
 import java.util.ArrayDeque;
 import java.util.Queue;
 
@@ -33,8 +35,6 @@
  */
 public class PolicySizeVerifier {
 
-    // Binary XML serializer doesn't support longer strings
-    public static final int MAX_POLICY_STRING_LENGTH = 65535;
     // FrameworkParsingPackageUtils#MAX_FILE_NAME_SIZE, Android packages are used in dir names.
     public static final int MAX_PACKAGE_NAME_LENGTH = 223;
 
@@ -47,8 +47,11 @@
      * Throw if string argument is too long to be serialized.
      */
     public static void enforceMaxStringLength(String str, String argName) {
-        Preconditions.checkArgument(
-                str.length() <= MAX_POLICY_STRING_LENGTH, argName + " loo long");
+        try {
+            long len = ModifiedUtf8.countBytes(str, /* throw error if too long */ true);
+        } catch (UTFDataFormatException e) {
+            throw new IllegalArgumentException(argName + " too long");
+        }
     }
 
     /**
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index 216ba5d..6797a51 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -22,15 +22,21 @@
 import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.annotation.UserHandleAware;
+import android.app.appsearch.AppSearchManager;
 import android.content.Context;
 import android.os.CancellationSignal;
 import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.ParcelableException;
 import android.os.RemoteException;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -45,10 +51,44 @@
 @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
 @SystemService(Context.APP_FUNCTION_SERVICE)
 public final class AppFunctionManager {
+
+    /**
+     * The default state of the app function. Call {@link #setAppFunctionEnabled} with this to reset
+     * enabled state to the default value.
+     */
+    public static final int APP_FUNCTION_STATE_DEFAULT = 0;
+
+    /**
+     * The app function is enabled. To enable an app function, call {@link #setAppFunctionEnabled}
+     * with this value.
+     */
+    public static final int APP_FUNCTION_STATE_ENABLED = 1;
+
+    /**
+     * The app function is disabled. To disable an app function, call {@link #setAppFunctionEnabled}
+     * with this value.
+     */
+    public static final int APP_FUNCTION_STATE_DISABLED = 2;
+
     private final IAppFunctionManager mService;
     private final Context mContext;
 
     /**
+     * The enabled state of the app function.
+     *
+     * @hide
+     */
+    @IntDef(
+            prefix = {"APP_FUNCTION_STATE_"},
+            value = {
+                APP_FUNCTION_STATE_DEFAULT,
+                APP_FUNCTION_STATE_ENABLED,
+                APP_FUNCTION_STATE_DISABLED
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EnabledState {}
+
+    /**
      * Creates an instance.
      *
      * @param service An interface to the backing service.
@@ -164,4 +204,118 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Returns a boolean through a callback, indicating whether the app function is enabled.
+     *
+     * <p>* This method can only check app functions owned by the caller, or those where the caller
+     * has visibility to the owner package and holds either the {@link
+     * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
+     * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
+     *
+     * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
+     *
+     * <ul>
+     *   <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
+     *       have access to it.
+     * </ul>
+     *
+     * @param functionIdentifier the identifier of the app function to check (unique within the
+     *     target package) and in most cases, these are automatically generated by the AppFunctions
+     *     SDK
+     * @param targetPackage the package name of the app function's owner
+     * @param executor the executor to run the request
+     * @param callback the callback to receive the function enabled check result
+     */
+    public void isAppFunctionEnabled(
+            @NonNull String functionIdentifier,
+            @NonNull String targetPackage,
+            @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Boolean, Exception> callback) {
+        Objects.requireNonNull(functionIdentifier);
+        Objects.requireNonNull(targetPackage);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        AppSearchManager appSearchManager = mContext.getSystemService(AppSearchManager.class);
+        if (appSearchManager == null) {
+            callback.onError(new IllegalStateException("Failed to get AppSearchManager."));
+            return;
+        }
+
+        AppFunctionManagerHelper.isAppFunctionEnabled(
+                functionIdentifier, targetPackage, appSearchManager, executor, callback);
+    }
+
+    /**
+     * Sets the enabled state of the app function owned by the calling package.
+     *
+     * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
+     *
+     * <ul>
+     *   <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
+     *       have access to it.
+     * </ul>
+     *
+     * @param functionIdentifier the identifier of the app function to enable (unique within the
+     *     calling package). In most cases, identifiers are automatically generated by the
+     *     AppFunctions SDK
+     * @param newEnabledState the new state of the app function
+     * @param executor the executor to run the callback
+     * @param callback the callback to receive the result of the function enablement. The call was
+     *     successful if no exception was thrown.
+     */
+    @UserHandleAware
+    public void setAppFunctionEnabled(
+            @NonNull String functionIdentifier,
+            @EnabledState int newEnabledState,
+            @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, Exception> callback) {
+        Objects.requireNonNull(functionIdentifier);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback);
+        try {
+            mService.setAppFunctionEnabled(
+                    mContext.getPackageName(),
+                    functionIdentifier,
+                    mContext.getUser(),
+                    newEnabledState,
+                    callbackWrapper);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private static class CallbackWrapper extends IAppFunctionEnabledCallback.Stub {
+
+        private final OutcomeReceiver<Void, Exception> mCallback;
+        private final Executor mExecutor;
+
+        CallbackWrapper(
+                @NonNull Executor callbackExecutor,
+                @NonNull OutcomeReceiver<Void, Exception> callback) {
+            mCallback = callback;
+            mExecutor = callbackExecutor;
+        }
+
+        @Override
+        public void onSuccess() {
+            mExecutor.execute(() -> mCallback.onResult(null));
+        }
+
+        @Override
+        public void onError(@NonNull ParcelableException exception) {
+            mExecutor.execute(() -> {
+                if (IllegalArgumentException.class.isAssignableFrom(
+                        exception.getCause().getClass())) {
+                    mCallback.onError((IllegalArgumentException) exception.getCause());
+                } else if (SecurityException.class.isAssignableFrom(
+                        exception.getCause().getClass())) {
+                    mCallback.onError((SecurityException) exception.getCause());
+                } else {
+                    mCallback.onError(exception);
+                }
+            });
+        }
+    }
 }
diff --git a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
index d6f45e4..fe2db49 100644
--- a/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
+++ b/core/java/android/app/appfunctions/AppFunctionManagerHelper.java
@@ -22,7 +22,7 @@
 import static android.app.appfunctions.AppFunctionStaticMetadataHelper.STATIC_PROPERTY_ENABLED_BY_DEFAULT;
 import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
 
-import android.annotation.CallbackExecutor;
+import android.Manifest;
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.app.appsearch.AppSearchManager;
@@ -33,6 +33,7 @@
 import android.app.appsearch.SearchResults;
 import android.app.appsearch.SearchSpec;
 import android.os.OutcomeReceiver;
+import android.text.TextUtils;
 
 import java.io.IOException;
 import java.util.List;
@@ -50,73 +51,69 @@
     /**
      * Returns (through a callback) a boolean indicating whether the app function is enabled.
      *
-     * <p>This method can only check app functions that are owned by the caller owned by packages
-     * visible to the caller.
+     * This method can only check app functions owned by the caller, or those where the caller
+     * has visibility to the owner package and holds either the {@link
+     * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
+     * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
      *
      * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
      *
      * <ul>
-     *   <li>{@link IllegalArgumentException}, if the function is not found
-     *   <li>{@link SecurityException}, if the caller does not have permission to query the target
-     *       package
+     *   <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
+     *       have access to it.
      * </ul>
      *
      * @param functionIdentifier the identifier of the app function to check (unique within the
-     *     target package) and in most cases, these are automatically generated by the AppFunctions
-     *     SDK
-     * @param targetPackage the package name of the app function's owner
-     * @param appSearchExecutor the executor to run the metadata search mechanism through AppSearch
-     * @param callbackExecutor the executor to run the callback
-     * @param callback the callback to receive the function enabled check result
+     *                           target package) and in most cases, these are automatically
+     *                           generated by the AppFunctions
+     *                           SDK
+     * @param targetPackage      the package name of the app function's owner
+     * @param executor           executor the executor to run the request
+     * @param callback           the callback to receive the function enabled check result
      * @hide
      */
     public static void isAppFunctionEnabled(
             @NonNull String functionIdentifier,
             @NonNull String targetPackage,
             @NonNull AppSearchManager appSearchManager,
-            @NonNull Executor appSearchExecutor,
-            @NonNull @CallbackExecutor Executor callbackExecutor,
+            @NonNull Executor executor,
             @NonNull OutcomeReceiver<Boolean, Exception> callback) {
         Objects.requireNonNull(functionIdentifier);
         Objects.requireNonNull(targetPackage);
         Objects.requireNonNull(appSearchManager);
-        Objects.requireNonNull(appSearchExecutor);
-        Objects.requireNonNull(callbackExecutor);
+        Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
         appSearchManager.createGlobalSearchSession(
-                appSearchExecutor,
+                executor,
                 (searchSessionResult) -> {
                     if (!searchSessionResult.isSuccess()) {
-                        callbackExecutor.execute(
-                                () ->
-                                        callback.onError(
-                                                failedResultToException(searchSessionResult)));
+                        callback.onError(failedResultToException(searchSessionResult));
                         return;
                     }
                     try (GlobalSearchSession searchSession = searchSessionResult.getResultValue()) {
                         SearchResults results =
                                 searchJoinedStaticWithRuntimeAppFunctions(
-                                        searchSession, targetPackage, functionIdentifier);
+                                        Objects.requireNonNull(searchSession),
+                                        targetPackage,
+                                        functionIdentifier);
                         results.getNextPage(
-                                appSearchExecutor,
-                                listAppSearchResult ->
-                                        callbackExecutor.execute(
-                                                () -> {
-                                                    if (listAppSearchResult.isSuccess()) {
-                                                        callback.onResult(
-                                                                getEnabledStateFromSearchResults(
-                                                                        Objects.requireNonNull(
-                                                                                listAppSearchResult
+                                executor,
+                                listAppSearchResult -> {
+                                    if (listAppSearchResult.isSuccess()) {
+                                        callback.onResult(
+                                                getEffectiveEnabledStateFromSearchResults(
+                                                        Objects.requireNonNull(
+                                                                listAppSearchResult
                                                                         .getResultValue())));
-                                                    } else {
-                                                        callback.onError(
-                                                                failedResultToException(
-                                                                        listAppSearchResult));
-                                                    }
-                                                }));
+                                    } else {
+                                        callback.onError(
+                                                failedResultToException(listAppSearchResult));
+                                    }
+                                });
+                        results.close();
                     } catch (Exception e) {
-                        callbackExecutor.execute(() -> callback.onError(e));
+                        callback.onError(e);
                     }
                 });
     }
@@ -124,56 +121,58 @@
     /**
      * Searches joined app function static and runtime metadata using the function Id and the
      * package.
-     *
-     * @hide
      */
     private static @NonNull SearchResults searchJoinedStaticWithRuntimeAppFunctions(
             @NonNull GlobalSearchSession session,
             @NonNull String targetPackage,
             @NonNull String functionIdentifier) {
         SearchSpec runtimeSearchSpec =
-                getAppFunctionRuntimeMetadataSearchSpecByFunctionId(targetPackage);
+                getAppFunctionRuntimeMetadataSearchSpecByPackageName(targetPackage);
         JoinSpec joinSpec =
                 new JoinSpec.Builder(PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID)
-                        .setNestedSearch(functionIdentifier, runtimeSearchSpec)
+                        .setNestedSearch(
+                                buildFilerRuntimeMetadataByFunctionIdQuery(functionIdentifier),
+                                runtimeSearchSpec)
                         .build();
         SearchSpec joinedStaticWithRuntimeSearchSpec =
                 new SearchSpec.Builder()
-                        .setJoinSpec(joinSpec)
                         .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
                         .addFilterSchemas(
                                 AppFunctionStaticMetadataHelper.getStaticSchemaNameForPackage(
                                         targetPackage))
-                        .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                        .setJoinSpec(joinSpec)
+                        .setVerbatimSearchEnabled(true)
                         .build();
-        return session.search(functionIdentifier, joinedStaticWithRuntimeSearchSpec);
+        return session.search(
+                buildFilerStaticMetadataByFunctionIdQuery(functionIdentifier),
+                joinedStaticWithRuntimeSearchSpec);
     }
 
     /**
-     * Finds whether the function is enabled or not from the search results returned by {@link
-     * #searchJoinedStaticWithRuntimeAppFunctions}.
+     * Returns whether the function is effectively enabled or not from the search results returned
+     * by {@link #searchJoinedStaticWithRuntimeAppFunctions}.
      *
+     * @param joinedStaticRuntimeResults search results joining AppFunctionStaticMetadata
+     *                                   and AppFunctionRuntimeMetadata.
      * @throws IllegalArgumentException if the function is not found in the results
-     * @hide
      */
-    private static boolean getEnabledStateFromSearchResults(
+    private static boolean getEffectiveEnabledStateFromSearchResults(
             @NonNull List<SearchResult> joinedStaticRuntimeResults) {
         if (joinedStaticRuntimeResults.isEmpty()) {
-            // Function not found.
             throw new IllegalArgumentException("App function not found.");
         } else {
             List<SearchResult> runtimeMetadataResults =
                     joinedStaticRuntimeResults.getFirst().getJoinedResults();
-            if (!runtimeMetadataResults.isEmpty()) {
-                Boolean result =
-                        (Boolean)
-                                runtimeMetadataResults
-                                        .getFirst()
-                                        .getGenericDocument()
-                                        .getProperty(PROPERTY_ENABLED);
-                if (result != null) {
-                    return result;
-                }
+            if (runtimeMetadataResults.isEmpty()) {
+                throw new IllegalArgumentException("App function not found.");
+            }
+            boolean[] enabled =
+                    runtimeMetadataResults
+                            .getFirst()
+                            .getGenericDocument()
+                            .getPropertyBooleanArray(PROPERTY_ENABLED);
+            if (enabled != null && enabled.length != 0) {
+                return enabled[0];
             }
             // Runtime metadata not found. Using the default value in the static metadata.
             return joinedStaticRuntimeResults
@@ -186,36 +185,39 @@
     /**
      * Returns search spec that queries app function metadata for a specific package name by its
      * function identifier.
-     *
-     * @hide
      */
-    public static @NonNull SearchSpec getAppFunctionRuntimeMetadataSearchSpecByFunctionId(
+    private static @NonNull SearchSpec getAppFunctionRuntimeMetadataSearchSpecByPackageName(
             @NonNull String targetPackage) {
         return new SearchSpec.Builder()
                 .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
                 .addFilterSchemas(
                         AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(targetPackage))
-                .addFilterProperties(
-                        AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(targetPackage),
-                        List.of(AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID))
-                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                .setVerbatimSearchEnabled(true)
                 .build();
     }
 
-    /**
-     * Converts a failed app search result codes into an exception.
-     *
-     * @hide
-     */
-    public static @NonNull Exception failedResultToException(
+    private static String buildFilerRuntimeMetadataByFunctionIdQuery(String functionIdentifier) {
+        return TextUtils.formatSimple("%s:\"%s\"",
+                AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID,
+                functionIdentifier);
+    }
+
+    private static String buildFilerStaticMetadataByFunctionIdQuery(String functionIdentifier) {
+        return TextUtils.formatSimple("%s:\"%s\"",
+                AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID,
+                functionIdentifier);
+    }
+
+    /** Converts a failed app search result codes into an exception. */
+    private static @NonNull Exception failedResultToException(
             @NonNull AppSearchResult appSearchResult) {
         return switch (appSearchResult.getResultCode()) {
-            case AppSearchResult.RESULT_INVALID_ARGUMENT ->
-                    new IllegalArgumentException(appSearchResult.getErrorMessage());
-            case AppSearchResult.RESULT_IO_ERROR ->
-                    new IOException(appSearchResult.getErrorMessage());
-            case AppSearchResult.RESULT_SECURITY_ERROR ->
-                    new SecurityException(appSearchResult.getErrorMessage());
+            case AppSearchResult.RESULT_INVALID_ARGUMENT -> new IllegalArgumentException(
+                    appSearchResult.getErrorMessage());
+            case AppSearchResult.RESULT_IO_ERROR -> new IOException(
+                    appSearchResult.getErrorMessage());
+            case AppSearchResult.RESULT_SECURITY_ERROR -> new SecurityException(
+                    appSearchResult.getErrorMessage());
             default -> new IllegalStateException(appSearchResult.getErrorMessage());
         };
     }
diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
index 83b5aa0..8b7f326 100644
--- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -204,11 +204,17 @@
                             packageName, functionId));
         }
 
+        public Builder(AppFunctionRuntimeMetadata original) {
+            this(original.getPackageName(), original.getFunctionId());
+            setEnabled(original.getEnabled());
+        }
+
         /**
          * Sets an indicator specifying if the function is enabled or not. This would override the
          * default enabled state in the static metadata ({@link
-         * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). Sets this to
-         * null to clear the override.
+         * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). Sets this to null
+         * to clear the override.
+         * TODO(369683073) Replace the tristate Boolean with IntDef EnabledState.
          */
         @NonNull
         public Builder setEnabled(@Nullable Boolean enabled) {
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index f6580e6..4ed0a1b 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -99,6 +99,9 @@
     /** The operation was timed out. */
     public static final int RESULT_TIMED_OUT = 5;
 
+    /** The caller tried to execute a disabled app function. */
+    public static final int RESULT_DISABLED = 6;
+
     /** The result code of the app function execution. */
     @ResultCode private final int mResultCode;
 
@@ -274,6 +277,7 @@
                 RESULT_INTERNAL_ERROR,
                 RESULT_INVALID_ARGUMENT,
                 RESULT_TIMED_OUT,
+                RESULT_DISABLED,
             })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ResultCode {}
diff --git a/core/java/android/app/appfunctions/IAppFunctionEnabledCallback.aidl b/core/java/android/app/appfunctions/IAppFunctionEnabledCallback.aidl
new file mode 100644
index 0000000..ced4155
--- /dev/null
+++ b/core/java/android/app/appfunctions/IAppFunctionEnabledCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appfunctions;
+
+import android.os.ParcelableException;
+
+/**
+ * @hide
+ */
+oneway interface IAppFunctionEnabledCallback {
+    void onSuccess();
+    void onError(in ParcelableException exception);
+}
diff --git a/core/java/android/app/appfunctions/IAppFunctionManager.aidl b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
index c63217f..72335e4 100644
--- a/core/java/android/app/appfunctions/IAppFunctionManager.aidl
+++ b/core/java/android/app/appfunctions/IAppFunctionManager.aidl
@@ -17,9 +17,11 @@
 package android.app.appfunctions;
 
 import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
+import android.app.appfunctions.IAppFunctionEnabledCallback;
 import android.app.appfunctions.IExecuteAppFunctionCallback;
 import android.os.ICancellationSignal;
 
+import android.os.UserHandle;
 /**
  * Defines the interface for apps to interact with the app function execution service
  * {@code AppFunctionManagerService} running in the system server process.
@@ -37,4 +39,15 @@
         in ExecuteAppFunctionAidlRequest request,
         in IExecuteAppFunctionCallback callback
     );
+
+    /**
+    * Sets an AppFunction's enabled state provided by {@link AppFunctionService} through the system.
+    */
+    void setAppFunctionEnabled(
+        in String callingPackage,
+        in String functionIdentifier,
+        in UserHandle userHandle,
+        int enabledState,
+        in IAppFunctionEnabledCallback callback
+    );
 }
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index b0e38e2..cff42fb 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -399,13 +399,10 @@
                 return -1;
             }
             case "enable-text" -> {
-                if (mViewerConfigReader != null) {
-                    mViewerConfigReader.loadViewerConfig(groups, logger);
-                }
-                return setTextLogging(true, logger, groups);
+                return startLoggingToLogcat(groups, logger);
             }
             case "disable-text" -> {
-                return setTextLogging(false, logger, groups);
+                return stopLoggingToLogcat(groups, logger);
             }
             default -> {
                 return unknownCommand(pw);
diff --git a/core/java/com/android/internal/protolog/Utils.java b/core/java/com/android/internal/protolog/Utils.java
index 1e6ba30..00ef80a 100644
--- a/core/java/com/android/internal/protolog/Utils.java
+++ b/core/java/com/android/internal/protolog/Utils.java
@@ -93,8 +93,7 @@
                     os.write(TAG, tag);
                     break;
                 default:
-                    throw new RuntimeException(
-                            "Unexpected field id " + pis.getFieldNumber());
+                    Log.e(LOG_TAG, "Unexpected field id " + pis.getFieldNumber());
             }
         }
 
@@ -126,8 +125,7 @@
                     os.write(LOCATION, pis.readString(LOCATION));
                     break;
                 default:
-                    throw new RuntimeException(
-                            "Unexpected field id " + pis.getFieldNumber());
+                    Log.e(LOG_TAG, "Unexpected field id " + pis.getFieldNumber());
             }
         }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
index bfccb29..e3a1d8a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
@@ -142,6 +142,19 @@
         }
     }
 
+    void abortTaskContainerRebuilding(@NonNull WindowContainerTransaction wct) {
+        // Clean-up the legacy states in the system
+        for (int i = mTaskFragmentInfos.size() - 1; i >= 0; i--) {
+            final TaskFragmentInfo info = mTaskFragmentInfos.valueAt(i);
+            mPresenter.deleteTaskFragment(wct, info.getFragmentToken());
+        }
+        mPresenter.setSavedState(new Bundle());
+
+        mParcelableTaskContainerDataList.clear();
+        mTaskFragmentInfos.clear();
+        mTaskFragmentParentInfos.clear();
+    }
+
     boolean hasPendingStateToRestore() {
         return !mParcelableTaskContainerDataList.isEmpty();
     }
@@ -196,6 +209,7 @@
 
             mController.onTaskFragmentParentRestored(wct, taskContainer.getTaskId(),
                     mTaskFragmentParentInfos.get(taskContainer.getTaskId()));
+            mTaskFragmentParentInfos.remove(taskContainer.getTaskId());
             restoredAny = true;
         }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index db4bb0e..8345b40 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -56,6 +56,7 @@
 import android.annotation.CallbackExecutor;
 import android.app.Activity;
 import android.app.ActivityClient;
+import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
@@ -280,7 +281,7 @@
             mSplitRules.clear();
             mSplitRules.addAll(rules);
 
-            if (!Flags.aeBackStackRestore() || !mPresenter.isRebuildTaskContainersNeeded()) {
+            if (!Flags.aeBackStackRestore() || !mPresenter.isWaitingToRebuildTaskContainers()) {
                 return;
             }
 
@@ -2893,6 +2894,36 @@
                 return;
             }
             synchronized (mLock) {
+                if (mPresenter.isWaitingToRebuildTaskContainers()) {
+                    Log.w(TAG, "Rebuilding aborted, clean up and restart");
+
+                    // Retrieve the Task intent.
+                    final int taskId = getTaskId(activity);
+                    Intent taskIntent = null;
+                    final ActivityManager am = activity.getSystemService(ActivityManager.class);
+                    final List<ActivityManager.AppTask> appTasks = am.getAppTasks();
+                    for (ActivityManager.AppTask appTask : appTasks) {
+                        if (appTask.getTaskInfo().taskId == taskId) {
+                            taskIntent = appTask.getTaskInfo().baseIntent.cloneFilter();
+                            break;
+                        }
+                    }
+
+                    // Clean up and abort the restoration
+                    // TODO(b/369488857): also to remove the non-organized activities in the Task?
+                    final TransactionRecord transactionRecord =
+                            mTransactionManager.startNewTransaction();
+                    final WindowContainerTransaction wct = transactionRecord.getTransaction();
+                    mPresenter.abortTaskContainerRebuilding(wct);
+                    transactionRecord.apply(false /* shouldApplyIndependently */);
+
+                    // Start the Task root activity.
+                    if (taskIntent != null) {
+                        activity.startActivity(taskIntent);
+                    }
+                    return;
+                }
+
                 final IBinder activityToken = activity.getActivityToken();
                 final IBinder initialTaskFragmentToken =
                         getTaskFragmentTokenFromActivityClientRecord(activity);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 0c0ded9..b498ee2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -187,10 +187,14 @@
         mBackupHelper.scheduleBackup();
     }
 
-    boolean isRebuildTaskContainersNeeded() {
+    boolean isWaitingToRebuildTaskContainers() {
         return mBackupHelper.hasPendingStateToRestore();
     }
 
+    void abortTaskContainerRebuilding(@NonNull WindowContainerTransaction wct) {
+        mBackupHelper.abortTaskContainerRebuilding(wct);
+    }
+
     boolean rebuildTaskContainers(@NonNull WindowContainerTransaction wct,
             @NonNull Set<EmbeddingRule> rules) {
         return mBackupHelper.rebuildTaskContainers(wct, rules);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index dcc2d93..b453f1d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -156,7 +156,7 @@
         mSplitController = splitController;
         for (ParcelableTaskFragmentContainerData tfData :
                 data.getParcelableTaskFragmentContainerDataList()) {
-            final TaskFragmentInfo info = taskFragmentInfoMap.get(tfData.mToken);
+            final TaskFragmentInfo info = taskFragmentInfoMap.remove(tfData.mToken);
             if (info != null && !info.isEmpty()) {
                 final TaskFragmentContainer container =
                         new TaskFragmentContainer(tfData, splitController, this);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index d3bed59..a2439a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -361,8 +361,11 @@
                     final int anim = getRotationAnimationHint(change, info, mDisplayController);
                     isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
                     if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
-                        startRotationAnimation(startTransaction, change, info, anim, animations,
-                                onAnimFinish);
+                        final int flags = wallpaperTransit != WALLPAPER_TRANSITION_NONE
+                                && Flags.commonSurfaceAnimator()
+                                ? ScreenRotationAnimation.FLAG_HAS_WALLPAPER : 0;
+                        startRotationAnimation(startTransaction, change, info, anim, flags,
+                                animations, onAnimFinish);
                         isDisplayRotationAnimationStarted = true;
                         continue;
                     }
@@ -414,7 +417,7 @@
                 if (change.getParent() == null && !change.hasFlags(FLAG_IS_DISPLAY)
                         && change.getStartRotation() != change.getEndRotation()) {
                     startRotationAnimation(startTransaction, change, info,
-                            ROTATION_ANIMATION_ROTATE, animations, onAnimFinish);
+                            ROTATION_ANIMATION_ROTATE, 0 /* flags */, animations, onAnimFinish);
                     continue;
                 }
             }
@@ -699,12 +702,12 @@
     }
 
     private void startRotationAnimation(SurfaceControl.Transaction startTransaction,
-            TransitionInfo.Change change, TransitionInfo info, int animHint,
+            TransitionInfo.Change change, TransitionInfo info, int animHint, int flags,
             ArrayList<Animator> animations, Runnable onAnimFinish) {
         final int rootIdx = TransitionUtil.rootIndexFor(change, info);
         final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext,
                 mTransactionPool, startTransaction, change, info.getRoot(rootIdx).getLeash(),
-                animHint);
+                animHint, flags);
         // The rotation animation may consist of 3 animations: fade-out screenshot, fade-in real
         // content, and background color. The item of "animGroup" will be removed if the sub
         // animation is finished. Then if the list becomes empty, the rotation animation is done.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 5802e2c..1a04997 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -25,12 +25,9 @@
 import static com.android.wm.shell.transition.Transitions.TAG;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ArgbEvaluator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.content.Context;
-import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
@@ -38,6 +35,7 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
+import android.view.animation.AccelerateInterpolator;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 import android.window.ScreenCapture;
@@ -74,6 +72,7 @@
  */
 class ScreenRotationAnimation {
     static final int MAX_ANIMATION_DURATION = 10 * 1000;
+    static final int FLAG_HAS_WALLPAPER = 1;
 
     private final Context mContext;
     private final TransactionPool mTransactionPool;
@@ -98,6 +97,12 @@
     private SurfaceControl mBackColorSurface;
     /** The leash using to animate screenshot layer. */
     private final SurfaceControl mAnimLeash;
+    /**
+     * The container with background color for {@link #mSurfaceControl}. It is only created if
+     * {@link #mSurfaceControl} may be translucent. E.g. visible wallpaper with alpha < 1 (dimmed).
+     * That prevents flickering of alpha blending.
+     */
+    private SurfaceControl mBackEffectSurface;
 
     // The current active animation to move from the old to the new rotated
     // state.  Which animation is run here will depend on the old and new
@@ -111,8 +116,8 @@
     /** Intensity of light/whiteness of the layout after rotation occurs. */
     private float mEndLuma;
 
-    ScreenRotationAnimation(Context context, TransactionPool pool,
-            Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash, int animHint) {
+    ScreenRotationAnimation(Context context, TransactionPool pool, Transaction t,
+            TransitionInfo.Change change, SurfaceControl rootLeash, int animHint, int flags) {
         mContext = context;
         mTransactionPool = pool;
         mAnimHint = animHint;
@@ -170,11 +175,20 @@
                 }
                 hardwareBuffer.close();
             }
+            if ((flags & FLAG_HAS_WALLPAPER) != 0) {
+                mBackEffectSurface = new SurfaceControl.Builder()
+                        .setCallsite("ShellRotationAnimation").setParent(rootLeash)
+                        .setEffectLayer().setOpaque(true).setName("BackEffect").build();
+                t.reparent(mSurfaceControl, mBackEffectSurface)
+                        .setColor(mBackEffectSurface,
+                                new float[] {mStartLuma, mStartLuma, mStartLuma})
+                        .show(mBackEffectSurface);
+            }
 
             t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
             t.show(mAnimLeash);
             // Crop the real content in case it contains a larger child layer, e.g. wallpaper.
-            t.setCrop(mSurfaceControl, new Rect(0, 0, mEndWidth, mEndHeight));
+            t.setCrop(getEnterSurface(), new Rect(0, 0, mEndWidth, mEndHeight));
 
             if (!isCustomRotate()) {
                 mBackColorSurface = new SurfaceControl.Builder()
@@ -202,6 +216,11 @@
         return mAnimHint == ROTATION_ANIMATION_CROSSFADE || mAnimHint == ROTATION_ANIMATION_JUMPCUT;
     }
 
+    /** Returns the surface which contains the real content to animate enter. */
+    private SurfaceControl getEnterSurface() {
+        return mBackEffectSurface != null ? mBackEffectSurface : mSurfaceControl;
+    }
+
     private void setScreenshotTransform(SurfaceControl.Transaction t) {
         if (mScreenshotLayer == null) {
             return;
@@ -314,7 +333,11 @@
         } else {
             startDisplayRotation(animations, finishCallback, mainExecutor);
             startScreenshotRotationAnimation(animations, finishCallback, mainExecutor);
-            //startColorAnimation(mTransaction, animationScale);
+            if (mBackEffectSurface != null && mStartLuma > 0.1f) {
+                // Animate from the color of background to black for smooth alpha blending.
+                buildLumaAnimation(animations, mStartLuma, 0f /* endLuma */, mBackEffectSurface,
+                        animationScale, finishCallback, mainExecutor);
+            }
         }
 
         return true;
@@ -322,7 +345,7 @@
 
     private void startDisplayRotation(@NonNull ArrayList<Animator> animations,
             @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
-        buildSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
+        buildSurfaceAnimation(animations, mRotateEnterAnimation, getEnterSurface(), finishCallback,
                 mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
                 null /* clipRect */, false /* isActivity */);
     }
@@ -341,40 +364,17 @@
                 null /* clipRect */, false /* isActivity */);
     }
 
-    private void startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor) {
-        int colorTransitionMs = mContext.getResources().getInteger(
-                R.integer.config_screen_rotation_color_transition);
-        final float[] rgbTmpFloat = new float[3];
-        final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
-        final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
-        final long duration = colorTransitionMs * (long) animationScale;
-        final Transaction t = mTransactionPool.acquire();
-
-        final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
-        // Animation length is already expected to be scaled.
-        va.overrideDurationScale(1.0f);
-        va.setDuration(duration);
-        va.addUpdateListener(animation -> {
-            final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
-            final float fraction = currentPlayTime / va.getDuration();
-            applyColor(startColor, endColor, rgbTmpFloat, fraction, mBackColorSurface, t);
-        });
-        va.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
-                        t);
-                mTransactionPool.release(t);
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
-                        t);
-                mTransactionPool.release(t);
-            }
-        });
-        animExecutor.execute(va::start);
+    private void buildLumaAnimation(@NonNull ArrayList<Animator> animations,
+            float startLuma, float endLuma, SurfaceControl surface, float animationScale,
+            @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
+        final long durationMillis = (long) (mContext.getResources().getInteger(
+                R.integer.config_screen_rotation_color_transition) * animationScale);
+        final LumaAnimation animation = new LumaAnimation(durationMillis);
+        // Align the end with the enter animation.
+        animation.setStartOffset(mRotateEnterAnimation.getDuration() - durationMillis);
+        final LumaAnimationAdapter adapter = new LumaAnimationAdapter(surface, startLuma, endLuma);
+        DefaultSurfaceAnimator.buildSurfaceAnimation(animations, animation, finishCallback,
+                mTransactionPool, mainExecutor, adapter);
     }
 
     public void kill() {
@@ -389,21 +389,47 @@
         if (mBackColorSurface != null && mBackColorSurface.isValid()) {
             t.remove(mBackColorSurface);
         }
+        if (mBackEffectSurface != null && mBackEffectSurface.isValid()) {
+            t.remove(mBackEffectSurface);
+        }
         t.apply();
         mTransactionPool.release(t);
     }
 
-    private static void applyColor(int startColor, int endColor, float[] rgbFloat,
-            float fraction, SurfaceControl surface, SurfaceControl.Transaction t) {
-        final int color = (Integer) ArgbEvaluator.getInstance().evaluate(fraction, startColor,
-                endColor);
-        Color middleColor = Color.valueOf(color);
-        rgbFloat[0] = middleColor.red();
-        rgbFloat[1] = middleColor.green();
-        rgbFloat[2] = middleColor.blue();
-        if (surface.isValid()) {
-            t.setColor(surface, rgbFloat);
+    /** A no-op wrapper to provide animation duration. */
+    private static class LumaAnimation extends Animation {
+        LumaAnimation(long durationMillis) {
+            setDuration(durationMillis);
         }
-        t.apply();
+    }
+
+    private static class LumaAnimationAdapter extends DefaultSurfaceAnimator.AnimationAdapter {
+        final float[] mColorArray = new float[3];
+        final float mStartLuma;
+        final float mEndLuma;
+        final AccelerateInterpolator mInterpolation;
+
+        LumaAnimationAdapter(@NonNull SurfaceControl leash, float startLuma, float endLuma) {
+            super(leash);
+            mStartLuma = startLuma;
+            mEndLuma = endLuma;
+            // Make the initial progress color lighter if the background is light. That avoids
+            // darker content when fading into the entering surface.
+            final float factor = Math.min(3f, (Math.max(0.5f, mStartLuma) - 0.5f) * 10);
+            Slog.d(TAG, "Luma=" + mStartLuma + " factor=" + factor);
+            mInterpolation = factor > 0.5f ? new AccelerateInterpolator(factor) : null;
+        }
+
+        @Override
+        void applyTransformation(ValueAnimator animator, long currentPlayTime) {
+            final float fraction = mInterpolation != null
+                    ? mInterpolation.getInterpolation(animator.getAnimatedFraction())
+                    : animator.getAnimatedFraction();
+            final float luma = mStartLuma + fraction * (mEndLuma - mStartLuma);
+            mColorArray[0] = luma;
+            mColorArray[1] = luma;
+            mColorArray[2] = luma;
+            mTransaction.setColor(mLeash, mColorArray);
+        }
     }
 }
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml
index 40dbbac..c8df15d 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
index 85715db..706c632 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
index 6c903a2..7df1675 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-service/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
index 6c903a2..7df1675 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/platinum/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
index f69a90c..d87c179 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
index b76d065..99969e7 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
index 041978c..19c3e40 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml
@@ -24,6 +24,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
index bf040d2..7505860 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml
@@ -24,6 +24,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="on"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="on"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index bbb1420..f0bcfe53 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -36,7 +36,7 @@
 
 MinikinFontSkia::MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData,
                                  size_t fontSize, std::string_view filePath, int ttcIndex,
-                                 const std::vector<minikin::FontVariation>& axes)
+                                 const minikin::VariationSettings& axes)
         : mTypeface(std::move(typeface))
         , mSourceId(sourceId)
         , mFontData(fontData)
@@ -123,12 +123,12 @@
     return mTtcIndex;
 }
 
-const std::vector<minikin::FontVariation>& MinikinFontSkia::GetAxes() const {
+const minikin::VariationSettings& MinikinFontSkia::GetAxes() const {
     return mAxes;
 }
 
 std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation(
-        const std::vector<minikin::FontVariation>& variations) const {
+        const minikin::VariationSettings& variations) const {
     SkFontArguments args;
 
     std::vector<SkFontArguments::VariationPosition::Coordinate> skVariation;
diff --git a/libs/hwui/hwui/MinikinSkia.h b/libs/hwui/hwui/MinikinSkia.h
index de9a5c2..7fe5978bfd 100644
--- a/libs/hwui/hwui/MinikinSkia.h
+++ b/libs/hwui/hwui/MinikinSkia.h
@@ -32,7 +32,7 @@
 public:
     MinikinFontSkia(sk_sp<SkTypeface> typeface, int sourceId, const void* fontData, size_t fontSize,
                     std::string_view filePath, int ttcIndex,
-                    const std::vector<minikin::FontVariation>& axes);
+                    const minikin::VariationSettings& axes);
 
     float GetHorizontalAdvance(uint32_t glyph_id, const minikin::MinikinPaint& paint,
                                const minikin::FontFakery& fakery) const override;
@@ -59,9 +59,9 @@
     size_t GetFontSize() const;
     int GetFontIndex() const;
     const std::string& getFilePath() const { return mFilePath; }
-    const std::vector<minikin::FontVariation>& GetAxes() const;
+    const minikin::VariationSettings& GetAxes() const;
     std::shared_ptr<minikin::MinikinFont> createFontWithVariation(
-            const std::vector<minikin::FontVariation>&) const;
+            const minikin::VariationSettings&) const;
     int GetSourceId() const override { return mSourceId; }
 
     static uint32_t packFontFlags(const SkFont&);
@@ -80,7 +80,7 @@
     const void* mFontData;
     size_t mFontSize;
     int mTtcIndex;
-    std::vector<minikin::FontVariation> mAxes;
+    minikin::VariationSettings mAxes;
     std::string mFilePath;
 };
 
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index a9d1a2a..2d812d6 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -92,8 +92,8 @@
     return result;
 }
 
-Typeface* Typeface::createFromTypefaceWithVariation(
-        Typeface* src, const std::vector<minikin::FontVariation>& variations) {
+Typeface* Typeface::createFromTypefaceWithVariation(Typeface* src,
+                                                    const minikin::VariationSettings& variations) {
     const Typeface* resolvedFace = Typeface::resolveDefault(src);
     Typeface* result = new Typeface();
     if (result != nullptr) {
@@ -192,9 +192,8 @@
     sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(fontData));
     LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont);
 
-    std::shared_ptr<minikin::MinikinFont> font =
-            std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, kRobotoFont,
-                                              0, std::vector<minikin::FontVariation>());
+    std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>(
+            std::move(typeface), 0, data, st.st_size, kRobotoFont, 0, minikin::VariationSettings());
     std::vector<std::shared_ptr<minikin::Font>> fonts;
     fonts.push_back(minikin::Font::Builder(font).build());
 
diff --git a/libs/hwui/hwui/Typeface.h b/libs/hwui/hwui/Typeface.h
index 565136e..2c96c1a 100644
--- a/libs/hwui/hwui/Typeface.h
+++ b/libs/hwui/hwui/Typeface.h
@@ -74,8 +74,8 @@
     static Typeface* createRelative(Typeface* src, Style desiredStyle);
     static Typeface* createAbsolute(Typeface* base, int weight, bool italic);
 
-    static Typeface* createFromTypefaceWithVariation(
-            Typeface* src, const std::vector<minikin::FontVariation>& variations);
+    static Typeface* createFromTypefaceWithVariation(Typeface* src,
+                                                     const minikin::VariationSettings& variations);
 
     static Typeface* createFromFamilies(
             std::vector<std::shared_ptr<minikin::FontFamily>>&& families, int weight, int italic,
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index e6d790f..9922ff3 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -133,9 +133,9 @@
         builder->axes.clear();
         return false;
     }
-    std::shared_ptr<minikin::MinikinFont> minikinFont =
-            std::make_shared<MinikinFontSkia>(std::move(face), fonts::getNewSourceId(), fontPtr,
-                                              fontSize, "", ttcIndex, builder->axes);
+    std::shared_ptr<minikin::MinikinFont> minikinFont = std::make_shared<MinikinFontSkia>(
+            std::move(face), fonts::getNewSourceId(), fontPtr, fontSize, "", ttcIndex,
+            minikin::VariationSettings(builder->axes, false));
     minikin::Font::Builder fontBuilder(minikinFont);
 
     if (weight != RESOLVE_BY_FONT_TABLE) {
diff --git a/libs/hwui/jni/Typeface.cpp b/libs/hwui/jni/Typeface.cpp
index 209b35c..0f458dd 100644
--- a/libs/hwui/jni/Typeface.cpp
+++ b/libs/hwui/jni/Typeface.cpp
@@ -80,7 +80,8 @@
         AxisHelper axis(env, axisObject);
         variations.push_back(minikin::FontVariation(axis.getTag(), axis.getStyleValue()));
     }
-    return toJLong(Typeface::createFromTypefaceWithVariation(toTypeface(familyHandle), variations));
+    return toJLong(Typeface::createFromTypefaceWithVariation(
+            toTypeface(familyHandle), minikin::VariationSettings(variations, false /* sorted */)));
 }
 
 static jlong Typeface_createWeightAlias(JNIEnv* env, jobject, jlong familyHandle, jint weight) {
@@ -273,7 +274,7 @@
     const std::string& path = typeface->GetFontPath();
     writer->writeString(path);
     writer->write<int>(typeface->GetFontIndex());
-    const std::vector<minikin::FontVariation>& axes = typeface->GetAxes();
+    const minikin::VariationSettings& axes = typeface->GetAxes();
     writer->writeArray<minikin::FontVariation>(axes.data(), axes.size());
     bool hasVerity = getVerity(path);
     writer->write<int8_t>(static_cast<int8_t>(hasVerity));
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index f405aba..6a05b6c 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -142,7 +142,7 @@
     std::shared_ptr<minikin::MinikinFont> newMinikinFont = std::make_shared<MinikinFontSkia>(
             std::move(newTypeface), minikinSkia->GetSourceId(), minikinSkia->GetFontData(),
             minikinSkia->GetFontSize(), minikinSkia->getFilePath(), minikinSkia->GetFontIndex(),
-            builder->axes);
+            minikin::VariationSettings(builder->axes, false));
     std::shared_ptr<minikin::Font> newFont = minikin::Font::Builder(newMinikinFont)
               .setWeight(weight)
               .setSlant(static_cast<minikin::FontStyle::Slant>(italic))
@@ -303,7 +303,7 @@
         var = reader.readArray<minikin::FontVariation>().first[index];
     } else {
         const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
-        var = minikinFont->GetAxes().at(index);
+        var = minikinFont->GetAxes()[index];
     }
     uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
     return (static_cast<uint64_t>(var.axisTag) << 32) | static_cast<uint64_t>(floatBinary);
diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp
index 91f78ce..0c07b2a 100644
--- a/native/android/system_fonts.cpp
+++ b/native/android/system_fonts.cpp
@@ -327,7 +327,7 @@
     result->mWeight = font->style().weight();
     result->mItalic = font->style().slant() == minikin::FontStyle::Slant::ITALIC;
     result->mCollectionIndex = minikinFontSkia->GetFontIndex();
-    const std::vector<minikin::FontVariation>& axes = minikinFontSkia->GetAxes();
+    const minikin::VariationSettings& axes = minikinFontSkia->GetAxes();
     result->mAxes.reserve(axes.size());
     for (auto axis : axes) {
         result->mAxes.push_back(std::make_pair(axis.axisTag, axis.value));
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index af07686..06f471e 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -42,6 +42,7 @@
         "SettingsLibFooterPreference",
         "SettingsLibHelpUtils",
         "SettingsLibIllustrationPreference",
+        "SettingsLibIntroPreference",
         "SettingsLibLayoutPreference",
         "SettingsLibMainSwitchPreference",
         "SettingsLibProfileSelector",
diff --git a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
index 47ce587..b06052a 100644
--- a/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
+++ b/packages/SettingsLib/AppPreference/res/layout-v33/preference_app.xml
@@ -78,15 +78,6 @@
             android:textAppearance="?android:attr/textAppearanceSmall"
             android:textColor="?android:attr/textColorSecondary"
             android:visibility="gone"/>
-
-        <ProgressBar
-            android:id="@android:id/progress"
-            style="?android:attr/progressBarStyleHorizontal"
-            android:layout_width="match_parent"
-            android:layout_height="4dp"
-            android:layout_marginTop="4dp"
-            android:max="100"
-            android:visibility="gone"/>
     </LinearLayout>
 
     <LinearLayout
diff --git a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
index e65f7de..ac57228 100644
--- a/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
+++ b/packages/SettingsLib/AppPreference/res/layout/preference_app.xml
@@ -74,15 +74,6 @@
             android:textAppearance="?android:attr/textAppearanceSmall"
             android:textColor="?android:attr/textColorSecondary"
             android:visibility="gone"/>
-
-        <ProgressBar
-            android:id="@android:id/progress"
-            style="?android:attr/progressBarStyleHorizontal"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginTop="4dp"
-            android:max="100"
-            android:visibility="gone"/>
     </LinearLayout>
 
     <LinearLayout
diff --git a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java
index f1d162e..3b52df7 100644
--- a/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java
+++ b/packages/SettingsLib/AppPreference/src/com/android/settingslib/widget/AppPreference.java
@@ -18,11 +18,8 @@
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.view.View;
-import android.widget.ProgressBar;
 
 import androidx.preference.Preference;
-import androidx.preference.PreferenceViewHolder;
 
 import com.android.settingslib.widget.preference.app.R;
 
@@ -31,9 +28,6 @@
  */
 public class AppPreference extends Preference {
 
-    private int mProgress;
-    private boolean mProgressVisible;
-
     public AppPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         setLayoutResource(R.layout.preference_app);
@@ -53,29 +47,4 @@
         super(context, attrs);
         setLayoutResource(R.layout.preference_app);
     }
-
-    /**
-     * Sets the current progress.
-     * @param amount the current progress
-     *
-     * @see ProgressBar#setProgress(int)
-     */
-    public void setProgress(int amount) {
-        mProgress = amount;
-        mProgressVisible = true;
-        notifyChanged();
-    }
-
-    @Override
-    public void onBindViewHolder(PreferenceViewHolder view) {
-        super.onBindViewHolder(view);
-
-        final ProgressBar progress = (ProgressBar) view.findViewById(android.R.id.progress);
-        if (mProgressVisible) {
-            progress.setProgress(mProgress);
-            progress.setVisibility(View.VISIBLE);
-        } else {
-            progress.setVisibility(View.GONE);
-        }
-    }
 }
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml
new file mode 100644
index 0000000..f55b320
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/settingslib_button"
+        style="@style/SettingsLibButtonStyle.Expressive.Filled" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml
new file mode 100644
index 0000000..b663b6c
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_extra.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/settingslib_button"
+        style="@style/SettingsLibButtonStyle.Expressive.Filled.Extra" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml
new file mode 100644
index 0000000..784e6ad
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_filled_large.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/settingslib_button"
+        style="@style/SettingsLibButtonStyle.Expressive.Filled.Large" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml
new file mode 100644
index 0000000..8b44a65
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/settingslib_button"
+        style="@style/SettingsLibButtonStyle.Expressive.Outline" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml
new file mode 100644
index 0000000..f8a2d8f
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_extra.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/settingslib_button"
+        style="@style/SettingsLibButtonStyle.Expressive.Outline.Extra" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml
new file mode 100644
index 0000000..781a5a1
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_outline_large.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/settingslib_button"
+        style="@style/SettingsLibButtonStyle.Expressive.Outline.Large" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml
new file mode 100644
index 0000000..5b568f8
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/settingslib_button"
+        style="@style/SettingsLibButtonStyle.Expressive.Tonal" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml
new file mode 100644
index 0000000..1e7a08b
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_extra.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/settingslib_button"
+        style="@style/SettingsLibButtonStyle.Expressive.Tonal.Extra" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml
new file mode 100644
index 0000000..42116be
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout-v35/settingslib_expressive_button_tonal_large.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/settingslib_button"
+        style="@style/SettingsLibButtonStyle.Expressive.Tonal.Large" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/ButtonPreference/res/values-v35/attrs_expressive.xml b/packages/SettingsLib/ButtonPreference/res/values-v35/attrs_expressive.xml
new file mode 100644
index 0000000..a1761e5
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values-v35/attrs_expressive.xml
@@ -0,0 +1,31 @@
+<?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>
+    <declare-styleable name="ButtonPreference">
+        <attr name="buttonType" format="enum">
+            <enum name="filled" value="0"/>
+            <enum name="tonal" value="1"/>
+            <enum name="outline" value="2"/>
+        </attr>
+        <attr name="buttonSize" format="enum">
+            <enum name="normal" value="0"/>
+            <enum name="large" value="1"/>
+            <enum name="extra" value="2"/>
+        </attr>
+    </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
index 16ba962..0041eb2 100644
--- a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
@@ -32,11 +32,44 @@
 
 import com.android.settingslib.widget.preference.button.R;
 
+import com.google.android.material.button.MaterialButton;
+
 /**
  * A preference handled a button
  */
 public class ButtonPreference extends Preference {
 
+    enum ButtonStyle {
+        FILLED_NORMAL(0, 0, R.layout.settingslib_expressive_button_filled),
+        FILLED_LARGE(0, 1, R.layout.settingslib_expressive_button_filled_large),
+        FILLED_EXTRA(0, 2, R.layout.settingslib_expressive_button_filled_extra),
+        TONAL_NORMAL(1, 0, R.layout.settingslib_expressive_button_tonal),
+        TONAL_LARGE(1, 1, R.layout.settingslib_expressive_button_tonal_large),
+        TONAL_EXTRA(1, 2, R.layout.settingslib_expressive_button_tonal_extra),
+        OUTLINE_NORMAL(2, 0, R.layout.settingslib_expressive_button_outline),
+        OUTLINE_LARGE(2, 1, R.layout.settingslib_expressive_button_outline_large),
+        OUTLINE_EXTRA(2, 2, R.layout.settingslib_expressive_button_outline_extra);
+
+        private final int mType;
+        private final int mSize;
+        private final int mLayoutId;
+
+        ButtonStyle(int type, int size, int layoutId) {
+            this.mType = type;
+            this.mSize = size;
+            this.mLayoutId = layoutId;
+        }
+
+        static int getLayoutId(int type, int size) {
+            for (ButtonStyle style : values()) {
+                if (style.mType == type && style.mSize == size) {
+                    return style.mLayoutId;
+                }
+            }
+            throw new IllegalArgumentException();
+        }
+    }
+
     private static final int ICON_SIZE = 24;
 
     private View.OnClickListener mClickListener;
@@ -86,7 +119,7 @@
     }
 
     private void init(Context context, AttributeSet attrs, int defStyleAttr) {
-        setLayoutResource(R.layout.settingslib_button_layout);
+        int resId = R.layout.settingslib_button_layout;
 
         if (attrs != null) {
             TypedArray a = context.obtainStyledAttributes(attrs,
@@ -102,8 +135,16 @@
                     R.styleable.ButtonPreference, defStyleAttr,
                     0 /*defStyleRes*/);
             mGravity = a.getInt(R.styleable.ButtonPreference_android_gravity, Gravity.START);
+
+            if (SettingsThemeHelper.isExpressiveTheme(context)) {
+                int type = a.getInt(R.styleable.ButtonPreference_buttonType, 0);
+                int size = a.getInt(R.styleable.ButtonPreference_buttonSize, 0);
+                resId = ButtonStyle.getLayoutId(type, size);
+            }
             a.recycle();
         }
+
+        setLayoutResource(resId);
     }
 
     @Override
@@ -144,14 +185,20 @@
         if (mButton == null || icon == null) {
             return;
         }
-        //get pixel from dp
-        int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ICON_SIZE,
-                getContext().getResources().getDisplayMetrics());
-        icon.setBounds(0, 0, size, size);
 
-        //set drawableStart
-        mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null/* top */, null/* end */,
-                null/* bottom */);
+        if (mButton instanceof MaterialButton) {
+            ((MaterialButton) mButton).setIcon(icon);
+        } else {
+            //get pixel from dp
+            int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ICON_SIZE,
+                    getContext().getResources().getDisplayMetrics());
+            icon.setBounds(0, 0, size, size);
+
+            //set drawableStart
+            mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null/* top */,
+                    null/* end */,
+                    null/* bottom */);
+        }
     }
 
     @Override
diff --git a/packages/SettingsLib/IntroPreference/Android.bp b/packages/SettingsLib/IntroPreference/Android.bp
new file mode 100644
index 0000000..155db18
--- /dev/null
+++ b/packages/SettingsLib/IntroPreference/Android.bp
@@ -0,0 +1,33 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+    name: "SettingsLibIntroPreference",
+    use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
+
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    resource_dirs: ["res"],
+
+    static_libs: [
+        "androidx.preference_preference",
+        "SettingsLibSettingsTheme",
+    ],
+
+    sdk_version: "system_current",
+    min_sdk_version: "21",
+    apex_available: [
+        "//apex_available:platform",
+    ],
+}
diff --git a/packages/SettingsLib/IntroPreference/AndroidManifest.xml b/packages/SettingsLib/IntroPreference/AndroidManifest.xml
new file mode 100644
index 0000000..f1bfee5
--- /dev/null
+++ b/packages/SettingsLib/IntroPreference/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.settingslib.widget.preference.intro">
+
+    <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
new file mode 100644
index 0000000..203a395
--- /dev/null
+++ b/packages/SettingsLib/IntroPreference/res/layout/settingslib_expressive_preference_intro.xml
@@ -0,0 +1,45 @@
+<?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.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/entity_header"
+    style="@style/SettingsLibEntityHeader">
+
+    <LinearLayout
+        android:id="@+id/entity_header_content"
+        style="@style/SettingsLibEntityHeaderContent">
+
+        <ImageView
+            android:id="@android:id/icon"
+            android:src="@drawable/settingslib_arrow_drop_down"
+            style="@style/SettingsLibEntityHeaderIcon"/>
+
+        <TextView
+            android:id="@android:id/title"
+            android:text="Title"
+            style="@style/SettingsLibEntityHeaderTitle"/>
+
+        <com.android.settingslib.widget.CollapsableTextView
+            android:id="@+id/collapsable_summary"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center"/>
+
+    </LinearLayout>
+
+</RelativeLayout>
diff --git a/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt b/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
new file mode 100644
index 0000000..c93ec2b
--- /dev/null
+++ b/packages/SettingsLib/IntroPreference/src/com/android/settingslib/widget/IntroPreference.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.widget
+
+import android.content.Context
+import android.os.Build
+import android.util.AttributeSet
+import androidx.annotation.RequiresApi
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import com.android.settingslib.widget.preference.intro.R
+
+class IntroPreference @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0
+) : Preference(context, attrs, defStyleAttr, defStyleRes) {
+
+    private var isCollapsable: Boolean = false
+    private var minLines: Int = 2
+
+    init {
+        layoutResource = R.layout.settingslib_expressive_preference_intro
+        isSelectable = false
+
+        initAttributes(context, attrs, defStyleAttr)
+    }
+
+    private fun initAttributes(context: Context, attrs: AttributeSet?, defStyleAttr: Int) {
+        context.obtainStyledAttributes(
+            attrs,
+            COLLAPSABLE_TEXT_VIEW_ATTRS, defStyleAttr, 0
+        ).apply {
+            isCollapsable = getBoolean(IS_COLLAPSABLE, false)
+            minLines = getInt(
+                MIN_LINES,
+                if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES
+            ).coerceIn(1, DEFAULT_MAX_LINES)
+            recycle()
+        }
+    }
+
+    override fun onBindViewHolder(holder: PreferenceViewHolder) {
+        super.onBindViewHolder(holder)
+        holder.isDividerAllowedBelow = false
+        holder.isDividerAllowedAbove = false
+
+        (holder.findViewById(R.id.collapsable_summary) as? CollapsableTextView)?.apply {
+            setCollapsable(isCollapsable)
+            setMinLines(minLines)
+            setText(summary.toString())
+        }
+    }
+
+    /**
+     * Sets whether the summary is collapsable.
+     * @param collapsable True if the summary should be collapsable, false otherwise.
+     */
+    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    fun setCollapsable(collapsable: Boolean) {
+        isCollapsable = collapsable
+        minLines = if (isCollapsable) DEFAULT_MIN_LINES else DEFAULT_MAX_LINES
+        notifyChanged()
+    }
+
+    /**
+     * Sets the minimum number of lines to display when collapsed.
+     * @param lines The minimum number of lines.
+     */
+    @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    fun setMinLines(lines: Int) {
+        minLines = lines.coerceIn(1, DEFAULT_MAX_LINES)
+        notifyChanged()
+    }
+
+    companion object {
+        private const val DEFAULT_MAX_LINES = 10
+        private const val DEFAULT_MIN_LINES = 2
+
+        private val COLLAPSABLE_TEXT_VIEW_ATTRS =
+            com.android.settingslib.widget.theme.R.styleable.CollapsableTextView
+        private val MIN_LINES =
+            com.android.settingslib.widget.theme.R.styleable.CollapsableTextView_android_minLines
+        private val IS_COLLAPSABLE =
+            com.android.settingslib.widget.theme.R.styleable.CollapsableTextView_isCollapsable
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Preference/Android.bp b/packages/SettingsLib/Preference/Android.bp
index 17852e8..e83e17c 100644
--- a/packages/SettingsLib/Preference/Android.bp
+++ b/packages/SettingsLib/Preference/Android.bp
@@ -22,3 +22,16 @@
     ],
     kotlincflags: ["-Xjvm-default=all"],
 }
+
+android_library {
+    name: "SettingsLibPreference-testutils",
+    srcs: ["testutils/**/*.kt"],
+    static_libs: [
+        "SettingsLibPreference",
+        "androidx.fragment_fragment-testing",
+        "androidx.test.core",
+        "androidx.test.ext.junit",
+        "flag-junit",
+        "truth",
+    ],
+}
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
index 5fcf478..5e69895 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBinding.kt
@@ -68,10 +68,13 @@
                 preference.icon = null
             }
             val context = preference.context
+            val isPreferenceScreen = preference is PreferenceScreen
             preference.peekExtras()?.clear()
             extras(context)?.let { preference.extras.putAll(it) }
             preference.title = getPreferenceTitle(context)
-            preference.summary = getPreferenceSummary(context)
+            if (!isPreferenceScreen) {
+                preference.summary = getPreferenceSummary(context)
+            }
             preference.isEnabled = isEnabled(context)
             preference.isVisible =
                 (this as? PreferenceAvailabilityProvider)?.isAvailable(context) != false
@@ -81,7 +84,7 @@
             // dependency here. This simplifies dependency management and avoid the
             // IllegalStateException when call Preference.setDependency
             preference.dependency = null
-            if (preference !is PreferenceScreen) { // avoid recursive loop when build graph
+            if (!isPreferenceScreen) { // avoid recursive loop when build graph
                 preference.fragment = (this as? PreferenceScreenCreator)?.fragmentClass()?.name
                 preference.intent = intent(context)
             }
diff --git a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt
new file mode 100644
index 0000000..4d5f85f
--- /dev/null
+++ b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt
@@ -0,0 +1,123 @@
+/*
+ * 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.preference
+
+import android.content.Context
+import android.platform.test.flag.junit.SetFlagsRule
+import android.util.Log
+import androidx.fragment.app.testing.FragmentScenario
+import androidx.preference.Preference
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.PreferenceGroup
+import androidx.preference.PreferenceScreen
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Test case for catalyst screen. */
+@RunWith(AndroidJUnit4::class)
+abstract class CatalystScreenTestCase {
+    @get:Rule val setFlagsRule = SetFlagsRule()
+
+    protected val context: Context = ApplicationProvider.getApplicationContext()
+
+    /** Catalyst screen. */
+    protected abstract val preferenceScreenCreator: PreferenceScreenCreator
+
+    /** Flag to control catalyst screen. */
+    protected abstract val flagName: String
+
+    /**
+     * Test to compare the preference screen hierarchy between legacy screen (flag is disabled) and
+     * catalyst screen (flag is enabled).
+     */
+    @Test
+    fun migration() {
+        enableCatalystScreen()
+        assertThat(preferenceScreenCreator.isFlagEnabled(context)).isTrue()
+        val catalystScreen = stringifyPreferenceScreen()
+        Log.i("Catalyst", catalystScreen)
+
+        disableCatalystScreen()
+        assertThat(preferenceScreenCreator.isFlagEnabled(context)).isFalse()
+        val legacyScreen = stringifyPreferenceScreen()
+
+        assertThat(catalystScreen).isEqualTo(legacyScreen)
+    }
+
+    /**
+     * Enables the catalyst screen.
+     *
+     * By default, enable the [flagName]. Override for more complex situation.
+     */
+    @Suppress("DEPRECATION")
+    protected open fun enableCatalystScreen() {
+        setFlagsRule.enableFlags(flagName)
+    }
+
+    /**
+     * Disables the catalyst screen (legacy screen is shown).
+     *
+     * By default, disable the [flagName]. Override for more complex situation.
+     */
+    @Suppress("DEPRECATION")
+    protected open fun disableCatalystScreen() {
+        setFlagsRule.disableFlags(flagName)
+    }
+
+    private fun stringifyPreferenceScreen(): String {
+        @Suppress("UNCHECKED_CAST")
+        val clazz = preferenceScreenCreator.fragmentClass() as Class<PreferenceFragmentCompat>
+        val builder = StringBuilder()
+        FragmentScenario.launch(clazz).use {
+            it.onFragment { fragment -> fragment.preferenceScreen.toString(builder) }
+        }
+        return builder.toString()
+    }
+
+    private fun Preference.toString(builder: StringBuilder, indent: String = "") {
+        val clazz = javaClass
+        builder.append(indent).append(clazz).append(" {\n")
+        val indent2 = "$indent  "
+        if (clazz != PreferenceScreen::class.java) {
+            key?.let { builder.append(indent2).append("key: \"$it\"\n") }
+        }
+        title?.let { builder.append(indent2).append("title: \"$it\"\n") }
+        summary?.let { builder.append(indent2).append("summary: \"$it\"\n") }
+        fragment?.let { builder.append(indent2).append("fragment: \"$it\"\n") }
+        builder.append(indent2).append("order: $order\n")
+        builder.append(indent2).append("isCopyingEnabled: $isCopyingEnabled\n")
+        builder.append(indent2).append("isEnabled: $isEnabled\n")
+        builder.append(indent2).append("isIconSpaceReserved: $isIconSpaceReserved\n")
+        if (clazz != Preference::class.java && clazz != PreferenceScreen::class.java) {
+            builder.append(indent2).append("isPersistent: $isPersistent\n")
+        }
+        builder.append(indent2).append("isSelectable: $isSelectable\n")
+        if (this is PreferenceGroup) {
+            val count = preferenceCount
+            builder.append(indent2).append("preferenceCount: $count\n")
+            val indent4 = "$indent2  "
+            for (index in 0..<count) {
+                getPreference(index).toString(builder, indent4)
+            }
+        }
+        builder.append(indent).append("}\n")
+    }
+}
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
index 250c27e..9c65905 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v35/styles_expressive.xml
@@ -290,4 +290,43 @@
         <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>
+        <item name="android:paddingEnd">@dimen/settingslib_expressive_space_small1</item>
+    </style>
+
+    <style name="SettingsLibEntityHeader" parent="EntityHeader">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:paddingStart">?android:attr/listPreferredItemPaddingStart</item>
+        <item name="android:paddingEnd">?android:attr/listPreferredItemPaddingEnd</item>
+    </style>
+
+    <style name="SettingsLibEntityHeaderContent">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_centerHorizontal">true</item>
+        <item name="android:orientation">vertical</item>
+        <item name="android:gravity">center_horizontal</item>
+    </style>
+
+    <style name="SettingsLibEntityHeaderIcon">
+        <item name="android:layout_width">@dimen/settingslib_expressive_space_large3</item>
+        <item name="android:layout_height">@dimen/settingslib_expressive_space_large3</item>
+        <item name="android:scaleType">fitCenter</item>
+        <item name="android:antialias">true</item>
+    </style>
+
+    <style name="SettingsLibEntityHeaderTitle">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_marginTop">@dimen/settingslib_expressive_space_small1</item>
+        <item name="android:singleLine">false</item>
+        <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>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 7139f5b4..2a251a5 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -30,9 +30,6 @@
 import com.android.settingslib.spa.gallery.editor.SettingsDropdownBoxPageProvider
 import com.android.settingslib.spa.gallery.editor.SettingsDropdownCheckBoxProvider
 import com.android.settingslib.spa.gallery.home.HomePageProvider
-import com.android.settingslib.spa.gallery.itemList.ItemListPageProvider
-import com.android.settingslib.spa.gallery.itemList.ItemOperatePageProvider
-import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
 import com.android.settingslib.spa.gallery.editor.SettingsOutlinedTextFieldPageProvider
 import com.android.settingslib.spa.gallery.editor.SettingsTextFieldPasswordPageProvider
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
@@ -66,10 +63,6 @@
  */
 enum class SettingsPageProviderEnum(val displayName: String) {
     HOME("home"),
-    PREFERENCE("preference"),
-    ARGUMENT("argument"),
-    ITEM_LIST("itemList"),
-    ITEM_OP_PAGE("itemOp"),
 
     // Add your SPPs
 }
@@ -101,9 +94,6 @@
                 ChartPageProvider,
                 DialogMainPageProvider,
                 NavDialogProvider,
-                ItemListPageProvider,
-                ItemOperatePageProvider,
-                OperateListPageProvider,
                 EditorMainPageProvider,
                 SettingsOutlinedTextFieldPageProvider,
                 SettingsDropdownBoxPageProvider,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/banner/BannerPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/banner/BannerPageProvider.kt
index 6edd917..c16d8bf 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/banner/BannerPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/banner/BannerPageProvider.kt
@@ -39,9 +39,7 @@
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -161,14 +159,12 @@
         }
     }
 
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = createSettingsPage())
-            .setUiLayoutFn {
-                Preference(object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                })
-            }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
     }
 
     private const val TITLE = "Sample Banner"
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
index b001cad..773d3d1 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonPageProvider.kt
@@ -23,9 +23,7 @@
 import androidx.compose.material.icons.outlined.WarningAmber
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.button.ActionButton
@@ -55,14 +53,12 @@
         }
     }
 
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = createSettingsPage())
-            .setUiLayoutFn {
-                Preference(object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                })
-            }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
     }
 }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt
index 7a6ae2c..6ceb395 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt
@@ -39,6 +39,7 @@
 private enum class WeekDay(val num: Int) {
     Sun(0), Mon(1), Tue(2), Wed(3), Thu(4), Fri(5), Sat(6),
 }
+
 private const val TITLE = "Sample Chart"
 
 object ChartPageProvider : SettingsPageProvider {
@@ -103,14 +104,12 @@
         return entryList
     }
 
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner)
-            .setUiLayoutFn {
-                Preference(object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                })
-            }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
     }
 }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
index 4e3fcee..c9c81aa 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
@@ -18,6 +18,7 @@
 
 import android.os.Bundle
 import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -55,13 +56,13 @@
         }.build(),
     )
 
-    fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner)
-        .setUiLayoutFn {
-            Preference(object : PreferenceModel {
-                override val title = TITLE
-                override val onClick = navigator(name)
-            })
-        }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
+    }
 
     override fun getTitle(arguments: Bundle?) = TITLE
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
index c511542..f2b4091 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/editor/EditorMainPageProvider.kt
@@ -17,8 +17,8 @@
 package com.android.settingslib.spa.gallery.editor
 
 import android.os.Bundle
+import androidx.compose.runtime.Composable
 import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
@@ -44,14 +44,12 @@
         )
     }
 
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = owner)
-            .setUiLayoutFn {
-                Preference(object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                })
-            }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
     }
 
     override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
index c4058a02..4d77ea1 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
@@ -20,10 +20,8 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.gallery.R
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
@@ -32,8 +30,6 @@
 import com.android.settingslib.spa.gallery.chart.ChartPageProvider
 import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
 import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
-import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
-import com.android.settingslib.spa.gallery.page.ArgumentPageModel
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
 import com.android.settingslib.spa.gallery.page.FooterPageProvider
 import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
@@ -48,35 +44,11 @@
 import com.android.settingslib.spa.gallery.ui.CopyablePageProvider
 import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider
 import com.android.settingslib.spa.widget.scaffold.HomeScaffold
+import com.android.settingslib.spa.widget.ui.Category
 
 object HomePageProvider : SettingsPageProvider {
     override val name = SettingsPageProviderEnum.HOME.name
     override val displayName = SettingsPageProviderEnum.HOME.displayName
-    private val owner = createSettingsPage()
-
-    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        return listOf(
-            PreferenceMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            OperateListPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(),
-            SearchScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            SuwScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            PagerMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            FooterPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            IllustrationPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            ProgressBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            LoadingBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            ChartPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            DialogMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            EditorMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            BannerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            CopyablePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-        )
-    }
 
     override fun getTitle(arguments: Bundle?): String {
         return SpaEnvironmentFactory.instance.appContext.getString(R.string.app_name)
@@ -85,14 +57,30 @@
     @Composable
     override fun Page(arguments: Bundle?) {
         val title = remember { getTitle(arguments) }
-        val entries = remember { buildEntry(arguments) }
         HomeScaffold(title) {
-            for (entry in entries) {
-                if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) {
-                    entry.UiLayout(ArgumentPageModel.buildArgument(intParam = 0))
-                } else {
-                    entry.UiLayout()
-                }
+            Category {
+                PreferenceMainPageProvider.Entry()
+            }
+            Category {
+                SearchScaffoldPageProvider.Entry()
+                SuwScaffoldPageProvider.Entry()
+                ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = 0)
+            }
+            Category {
+                SliderPageProvider.Entry()
+                SpinnerPageProvider.Entry()
+                PagerMainPageProvider.Entry()
+                FooterPageProvider.Entry()
+                IllustrationPageProvider.Entry()
+                CategoryPageProvider.Entry()
+                ActionButtonPageProvider.Entry()
+                ProgressBarPageProvider.Entry()
+                LoadingBarPageProvider.Entry()
+                ChartPageProvider.Entry()
+                DialogMainPageProvider.Entry()
+                EditorMainPageProvider.Entry()
+                BannerPageProvider.Entry()
+                CopyablePageProvider.Entry()
             }
         }
     }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt
deleted file mode 100644
index 5f251b1..0000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.gallery.itemList
-
-import android.os.Bundle
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.core.os.bundleOf
-import androidx.navigation.NavType
-import androidx.navigation.navArgument
-import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.util.getStringArg
-import com.android.settingslib.spa.framework.util.navLink
-import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.widget.preference.Preference
-import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
-
-private const val OPERATOR_PARAM_NAME = "opParam"
-
-object ItemListPageProvider : SettingsPageProvider {
-    override val name = SettingsPageProviderEnum.ITEM_LIST.name
-    override val displayName = SettingsPageProviderEnum.ITEM_LIST.displayName
-    override val parameter = listOf(
-        navArgument(OPERATOR_PARAM_NAME) { type = NavType.StringType },
-    )
-
-    override fun getTitle(arguments: Bundle?): String {
-        val operation = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments) ?: "NULL"
-        return "Operation: $operation"
-    }
-
-    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        if (!ItemOperatePageProvider.isValidArgs(arguments)) return emptyList()
-        val operation = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments)!!
-        val owner = createSettingsPage(arguments)
-        return listOf(
-            ItemOperatePageProvider.buildInjectEntry(operation)!!.setLink(fromPage = owner).build(),
-        )
-    }
-
-    fun buildInjectEntry(opParam: String): SettingsEntryBuilder? {
-        val arguments = bundleOf(OPERATOR_PARAM_NAME to opParam)
-        if (!ItemOperatePageProvider.isValidArgs(arguments)) return null
-
-        return SettingsEntryBuilder.createInject(
-            owner = createSettingsPage(arguments),
-            label = "ItemList_$opParam",
-        ).setUiLayoutFn {
-            Preference(
-                object : PreferenceModel {
-                    override val title = opParam
-                    override val onClick = navigator(
-                        SettingsPageProviderEnum.ITEM_LIST.name + parameter.navLink(it)
-                    )
-                }
-            )
-        }.setSearchDataFn {
-            EntrySearchData(title = "Operation: $opParam")
-        }
-    }
-
-    @Composable
-    override fun Page(arguments: Bundle?) {
-        val title = remember { getTitle(arguments) }
-        val entries = remember { buildEntry(arguments) }
-        val itemList = remember {
-            // Add logic to get item List during runtime.
-            listOf("itemFoo", "itemBar", "itemToy")
-        }
-        RegularScaffold(title) {
-            for (item in itemList) {
-                val rtArgs = ItemOperatePageProvider.genRuntimeArguments(item)
-                for (entry in entries) {
-                    entry.UiLayout(rtArgs)
-                }
-            }
-        }
-    }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt
deleted file mode 100644
index 6caec07..0000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.gallery.itemList
-
-import android.os.Bundle
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.runtime.setValue
-import androidx.core.os.bundleOf
-import androidx.navigation.NavType
-import androidx.navigation.navArgument
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.util.getStringArg
-import com.android.settingslib.spa.framework.util.navLink
-import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.widget.preference.Preference
-import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.preference.SwitchPreference
-import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-
-private const val OPERATOR_PARAM_NAME = "opParam"
-private const val ITEM_NAME_PARAM_NAME = "rt_nameParam"
-private val ALLOWED_OPERATOR_LIST = listOf("opDnD", "opPiP", "opInstall", "opConnect")
-
-object ItemOperatePageProvider : SettingsPageProvider {
-    override val name = SettingsPageProviderEnum.ITEM_OP_PAGE.name
-    override val displayName = SettingsPageProviderEnum.ITEM_OP_PAGE.displayName
-    override val parameter = listOf(
-        navArgument(OPERATOR_PARAM_NAME) { type = NavType.StringType },
-        navArgument(ITEM_NAME_PARAM_NAME) { type = NavType.StringType },
-    )
-
-    override fun getTitle(arguments: Bundle?): String {
-        // Operation name is not a runtime parameter, which should always available
-        val operation = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments) ?: "opInValid"
-        // Item name is a runtime parameter, which could be missing
-        val itemName = parameter.getStringArg(ITEM_NAME_PARAM_NAME, arguments) ?: "[unset]"
-        return "$operation on $itemName"
-    }
-
-    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        if (!isValidArgs(arguments)) return emptyList()
-
-        val owner = createSettingsPage(arguments)
-        val entryList = mutableListOf<SettingsEntry>()
-        entryList.add(
-            SettingsEntryBuilder.create("ItemName", owner)
-                .setUiLayoutFn {
-                    // Item name is a runtime parameter, which needs to be read inside UiLayoutFn
-                    val itemName = parameter.getStringArg(ITEM_NAME_PARAM_NAME, it) ?: "NULL"
-                    Preference(
-                        object : PreferenceModel {
-                            override val title = "Item $itemName"
-                        }
-                    )
-                }.build()
-        )
-
-        // Operation name is not a runtime parameter, which can be read outside.
-        val opName = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments)!!
-        entryList.add(
-            SettingsEntryBuilder.create("ItemOp", owner)
-                .setUiLayoutFn {
-                    var checked by rememberSaveable { mutableStateOf(false) }
-                    SwitchPreference(remember {
-                        object : SwitchPreferenceModel {
-                            override val title = "Item operation: $opName"
-                            override val checked = { checked }
-                            override val onCheckedChange =
-                                { newChecked: Boolean -> checked = newChecked }
-                        }
-                    })
-                }.build(),
-        )
-        return entryList
-    }
-
-    fun buildInjectEntry(opParam: String): SettingsEntryBuilder? {
-        val arguments = bundleOf(OPERATOR_PARAM_NAME to opParam)
-        if (!isValidArgs(arguments)) return null
-
-        return SettingsEntryBuilder.createInject(
-            owner = createSettingsPage(arguments),
-            label = "ItemOp_$opParam",
-        ).setUiLayoutFn {
-            // Item name is a runtime parameter, which needs to be read inside UiLayoutFn
-            val itemName = parameter.getStringArg(ITEM_NAME_PARAM_NAME, it) ?: "NULL"
-            Preference(
-                object : PreferenceModel {
-                    override val title = "item: $itemName"
-                    override val onClick = navigator(
-                        SettingsPageProviderEnum.ITEM_OP_PAGE.name + parameter.navLink(it)
-                    )
-                }
-            )
-        }
-    }
-
-    fun isValidArgs(arguments: Bundle?): Boolean {
-        val opParam = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments)
-        return (opParam != null && ALLOWED_OPERATOR_LIST.contains(opParam))
-    }
-
-    fun genRuntimeArguments(itemName: String): Bundle {
-        return bundleOf(ITEM_NAME_PARAM_NAME to itemName)
-    }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/OperateListPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/OperateListPage.kt
deleted file mode 100644
index e0baf86..0000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/OperateListPage.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.gallery.itemList
-
-import android.os.Bundle
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.widget.preference.Preference
-import com.android.settingslib.spa.widget.preference.PreferenceModel
-
-private const val TITLE = "Operate List Main"
-
-object OperateListPageProvider : SettingsPageProvider {
-    override val name = "OpList"
-    private val owner = createSettingsPage()
-
-    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        return listOf(
-            ItemListPageProvider.buildInjectEntry("opPiP")!!.setLink(fromPage = owner).build(),
-            ItemListPageProvider.buildInjectEntry("opInstall")!!.setLink(fromPage = owner).build(),
-            ItemListPageProvider.buildInjectEntry("opDnD")!!.setLink(fromPage = owner).build(),
-            ItemListPageProvider.buildInjectEntry("opConnect")!!.setLink(fromPage = owner).build(),
-        )
-    }
-
-    override fun getTitle(arguments: Bundle?): String {
-        return TITLE
-    }
-
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = owner)
-            .setUiLayoutFn {
-                Preference(object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                })
-            }
-    }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index f01ff38..9ad1c22 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -18,112 +18,69 @@
 
 import android.os.Bundle
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
 import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 
+private const val TITLE = "Sample page with arguments"
+private const val STRING_PARAM_NAME = "stringParam"
+private const val INT_PARAM_NAME = "intParam"
+
 object ArgumentPageProvider : SettingsPageProvider {
-    // Defines all entry name in this page.
-    // Note that entry name would be used in log. DO NOT change it once it is set.
-    // One can still change the display name for better readability if necessary.
-    private enum class EntryEnum(val displayName: String) {
-        STRING_PARAM("string_param"),
-        INT_PARAM("int_param"),
-    }
+    override val name = "Argument"
 
-    private fun createEntry(owner: SettingsPage, entry: EntryEnum): SettingsEntryBuilder {
-        return SettingsEntryBuilder.create(owner, entry.name, entry.displayName)
-    }
-
-    override val name = SettingsPageProviderEnum.ARGUMENT.name
-    override val displayName = SettingsPageProviderEnum.ARGUMENT.displayName
-    override val parameter = ArgumentPageModel.parameter
-
-    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        if (!ArgumentPageModel.isValidArgument(arguments)) return emptyList()
-
-        val owner = createSettingsPage(arguments)
-        val entryList = mutableListOf<SettingsEntry>()
-        entryList.add(
-            createEntry(owner, EntryEnum.STRING_PARAM)
-                // Set attributes
-                .setIsSearchDataDynamic(true)
-                .setSearchDataFn { ArgumentPageModel.genStringParamSearchData() }
-                .setUiLayoutFn {
-                    // Set ui rendering
-                    Preference(ArgumentPageModel.create(it).genStringParamPreferenceModel())
-                }.build()
-        )
-
-        entryList.add(
-            createEntry(owner, EntryEnum.INT_PARAM)
-                // Set attributes
-                .setIsSearchDataDynamic(true)
-                .setSearchDataFn { ArgumentPageModel.genIntParamSearchData() }
-                .setUiLayoutFn {
-                    // Set ui rendering
-                    Preference(ArgumentPageModel.create(it).genIntParamPreferenceModel())
-                }.build()
-        )
-
-        entryList.add(buildInjectEntry("foo")!!.setLink(fromPage = owner).build())
-        entryList.add(buildInjectEntry("bar")!!.setLink(fromPage = owner).build())
-
-        return entryList
-    }
-
-    fun buildInjectEntry(stringParam: String): SettingsEntryBuilder? {
-        val arguments = ArgumentPageModel.buildArgument(stringParam)
-        if (!ArgumentPageModel.isValidArgument(arguments)) return null
-
-        return SettingsEntryBuilder.createInject(
-            owner = createSettingsPage(arguments),
-            label = "${name}_$stringParam",
-        )
-            .setSearchDataFn { ArgumentPageModel.genInjectSearchData() }
-            .setUiLayoutFn {
-                // Set ui rendering
-                Preference(ArgumentPageModel.create(it).genInjectPreferenceModel())
-            }
-    }
-
-    override fun getTitle(arguments: Bundle?): String {
-        return ArgumentPageModel.genPageTitle()
-    }
+    override val parameter = listOf(
+        navArgument(STRING_PARAM_NAME) { type = NavType.StringType },
+        navArgument(INT_PARAM_NAME) { type = NavType.IntType },
+    )
 
     @Composable
     override fun Page(arguments: Bundle?) {
-        val title = remember { getTitle(arguments) }
-        val entries = remember { buildEntry(arguments) }
-        val rtArgNext = remember { ArgumentPageModel.buildNextArgument(arguments) }
-        RegularScaffold(title) {
-            for (entry in entries) {
-                if (entry.toPage != null) {
-                    entry.UiLayout(rtArgNext)
-                } else {
-                    entry.UiLayout()
-                }
-            }
-        }
+        ArgumentPage(
+            stringParam = arguments!!.getString(STRING_PARAM_NAME, "default"),
+            intParam = arguments.getInt(INT_PARAM_NAME),
+        )
+    }
+
+    @Composable
+    fun EntryItem(stringParam: String, intParam: Int) {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val summary = { "$STRING_PARAM_NAME=$stringParam, $INT_PARAM_NAME=$intParam" }
+            override val onClick = navigator("$name/$stringParam/$intParam")
+        })
+    }
+}
+
+@Composable
+fun ArgumentPage(stringParam: String, intParam: Int) {
+    RegularScaffold(title = TITLE) {
+        Preference(object : PreferenceModel {
+            override val title = "String param value"
+            override val summary = { stringParam }
+        })
+
+        Preference(object : PreferenceModel {
+            override val title = "Int param value"
+            override val summary = { intParam.toString() }
+        })
+
+        ArgumentPageProvider.EntryItem(stringParam = "foo", intParam = intParam + 1)
+
+        ArgumentPageProvider.EntryItem(stringParam = "bar", intParam = intParam + 1)
     }
 }
 
 @Preview(showBackground = true)
 @Composable
 private fun ArgumentPagePreview() {
-    SpaEnvironmentFactory.resetForPreview()
     SettingsTheme {
-        ArgumentPageProvider.Page(
-            ArgumentPageModel.buildArgument(stringParam = "foo", intParam = 0)
-        )
+        ArgumentPage(stringParam = "foo", intParam = 0)
     }
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
deleted file mode 100644
index d763f77..0000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.gallery.page
-
-import android.os.Bundle
-import androidx.compose.runtime.Composable
-import androidx.lifecycle.viewmodel.compose.viewModel
-import androidx.navigation.NavType
-import androidx.navigation.navArgument
-import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.PageModel
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.compose.navigator
-import com.android.settingslib.spa.framework.util.getIntArg
-import com.android.settingslib.spa.framework.util.getStringArg
-import com.android.settingslib.spa.framework.util.navLink
-import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.widget.preference.PreferenceModel
-
-private const val TAG = "ArgumentPageModel"
-
-// Defines all the resources for this page.
-// In real Settings App, resources data is defined in xml, rather than SPP.
-private const val PAGE_TITLE = "Sample page with arguments"
-private const val STRING_PARAM_TITLE = "String param value"
-private const val INT_PARAM_TITLE = "Int param value"
-private const val STRING_PARAM_NAME = "stringParam"
-private const val INT_PARAM_NAME = "rt_intParam"
-private val ARGUMENT_PAGE_KEYWORDS = listOf("argument keyword1", "argument keyword2")
-
-class ArgumentPageModel : PageModel() {
-
-    companion object {
-        val parameter = listOf(
-            navArgument(STRING_PARAM_NAME) { type = NavType.StringType },
-            navArgument(INT_PARAM_NAME) { type = NavType.IntType },
-        )
-
-        fun buildArgument(stringParam: String? = null, intParam: Int? = null): Bundle {
-            val args = Bundle()
-            if (stringParam != null) args.putString(STRING_PARAM_NAME, stringParam)
-            if (intParam != null) args.putInt(INT_PARAM_NAME, intParam)
-            return args
-        }
-
-        fun buildNextArgument(arguments: Bundle? = null): Bundle {
-            val intParam = parameter.getIntArg(INT_PARAM_NAME, arguments)
-            val nextIntParam = if (intParam != null) intParam + 1 else null
-            return buildArgument(intParam = nextIntParam)
-        }
-
-        fun isValidArgument(arguments: Bundle?): Boolean {
-            val stringParam = parameter.getStringArg(STRING_PARAM_NAME, arguments)
-            return (stringParam != null && listOf("foo", "bar").contains(stringParam))
-        }
-
-        fun genStringParamSearchData(): EntrySearchData {
-            return EntrySearchData(title = STRING_PARAM_TITLE)
-        }
-
-        fun genIntParamSearchData(): EntrySearchData {
-            return EntrySearchData(title = INT_PARAM_TITLE)
-        }
-
-        fun genInjectSearchData(): EntrySearchData {
-            return EntrySearchData(title = PAGE_TITLE, keyword = ARGUMENT_PAGE_KEYWORDS)
-        }
-
-        fun genPageTitle(): String {
-            return PAGE_TITLE
-        }
-
-        @Composable
-        fun create(arguments: Bundle?): ArgumentPageModel {
-            val pageModel: ArgumentPageModel = viewModel(key = arguments.toString())
-            pageModel.initOnce(arguments)
-            return pageModel
-        }
-    }
-
-    private var arguments: Bundle? = null
-    private var stringParam: String? = null
-    private var intParam: Int? = null
-
-    override fun initialize(arguments: Bundle?) {
-        SpaEnvironmentFactory.instance.logger.message(
-            TAG, "Initialize with args " + arguments.toString()
-        )
-        this.arguments = arguments
-        stringParam = parameter.getStringArg(STRING_PARAM_NAME, arguments)
-        intParam = parameter.getIntArg(INT_PARAM_NAME, arguments)
-    }
-
-    @Composable
-    fun genStringParamPreferenceModel(): PreferenceModel {
-        return object : PreferenceModel {
-            override val title = STRING_PARAM_TITLE
-            override val summary = { stringParam!! }
-        }
-    }
-
-    @Composable
-    fun genIntParamPreferenceModel(): PreferenceModel {
-        return object : PreferenceModel {
-            override val title = INT_PARAM_TITLE
-            override val summary = { intParam!!.toString() }
-        }
-    }
-
-    @Composable
-    fun genInjectPreferenceModel(): PreferenceModel {
-        val summaryArray = listOf(
-            "$STRING_PARAM_NAME=" + stringParam!!,
-            "$INT_PARAM_NAME=" + intParam!!
-        )
-        return object : PreferenceModel {
-            override val title = PAGE_TITLE
-            override val summary = { summaryArray.joinToString(", ") }
-            override val onClick = navigator(
-                SettingsPageProviderEnum.ARGUMENT.name + parameter.navLink(arguments)
-            )
-        }
-    }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
index 345b47a..d31dab3 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
@@ -43,7 +43,7 @@
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
-            SettingsEntryBuilder.create( "Some Preference", owner)
+            SettingsEntryBuilder.create("Some Preference", owner)
                 .setSearchDataFn { EntrySearchData(title = "Some Preference") }
                 .setUiLayoutFn {
                     Preference(remember {
@@ -58,14 +58,12 @@
         return entryList
     }
 
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner)
-            .setUiLayoutFn {
-                Preference(object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                })
-            }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
     }
 
     override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPageProvider.kt
similarity index 86%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPageProvider.kt
index ee22b96..021e84f 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPageProvider.kt
@@ -41,7 +41,7 @@
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
-            SettingsEntryBuilder.create( "Lottie Illustration", owner)
+            SettingsEntryBuilder.create("Lottie Illustration", owner)
                 .setUiLayoutFn {
                     Preference(object : PreferenceModel {
                         override val title = "Lottie Illustration"
@@ -54,7 +54,7 @@
                 }.build()
         )
         entryList.add(
-            SettingsEntryBuilder.create( "Image Illustration", owner)
+            SettingsEntryBuilder.create("Image Illustration", owner)
                 .setUiLayoutFn {
                     Preference(object : PreferenceModel {
                         override val title = "Image Illustration"
@@ -70,14 +70,12 @@
         return entryList
     }
 
-     fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner)
-            .setUiLayoutFn {
-                Preference(object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                })
-            }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
     }
 
     override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPageProvider.kt
index f1cbc37..4d47481 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPageProvider.kt
@@ -30,9 +30,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -47,14 +45,12 @@
 object LoadingBarPageProvider : SettingsPageProvider {
     override val name = "LoadingBar"
 
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = createSettingsPage())
-            .setUiLayoutFn {
-                Preference(object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                })
-            }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
     }
 
     override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPageProvider.kt
similarity index 88%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPageProvider.kt
index 9026a24..47c49fe 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPageProvider.kt
@@ -27,9 +27,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.preference.Preference
@@ -46,14 +44,12 @@
 object ProgressBarPageProvider : SettingsPageProvider {
     override val name = "ProgressBar"
 
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = createSettingsPage())
-            .setUiLayoutFn {
-                Preference(object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                })
-            }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
     }
 
     override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPageProvider.kt
index 89b10ee..572746b 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPageProvider.kt
@@ -117,15 +117,14 @@
         return entryList
     }
 
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
-            Preference(
-                object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                }
-            )
-        }
+    @Composable
+    fun Entry() {
+        Preference(
+            object : PreferenceModel {
+                override val title = TITLE
+                override val onClick = navigator(name)
+            }
+        )
     }
 
     override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
index 603fcee..b83a026 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
@@ -50,15 +50,12 @@
         return entryList
     }
 
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
-            Preference(
-                object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                }
-            )
-        }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
     }
 
     override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt
index d7de9b4..3bb526e 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ListPreferencePageProvider.kt
@@ -23,9 +23,7 @@
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.preference.ListPreference
@@ -33,6 +31,8 @@
 import com.android.settingslib.spa.widget.preference.ListPreferenceOption
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.flow
@@ -41,30 +41,24 @@
 
 object ListPreferencePageProvider : SettingsPageProvider {
     override val name = "ListPreference"
-    private val owner = createSettingsPage()
 
-    override fun buildEntry(arguments: Bundle?) = listOf(
-        SettingsEntryBuilder.create("ListPreference", owner)
-            .setUiLayoutFn {
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        RegularScaffold(TITLE) {
+            Category {
                 SampleListPreference()
-            }.build(),
-        SettingsEntryBuilder.create("ListPreference not changeable", owner)
-            .setUiLayoutFn {
                 SampleNotChangeableListPreference()
-            }.build(),
-    )
-
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner)
-            .setUiLayoutFn {
-                Preference(object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                })
             }
+        }
     }
 
-    override fun getTitle(arguments: Bundle?) = TITLE
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
+    }
 }
 
 @Composable
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePageProvider.kt
similarity index 91%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePageProvider.kt
index 0d85c0e3..f548160 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePageProvider.kt
@@ -59,14 +59,12 @@
         return entryList
     }
 
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner)
-            .setUiLayoutFn {
-                Preference(object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                })
-            }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
     }
 
     override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
index 1626b02..831b439 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
@@ -17,45 +17,44 @@
 package com.android.settingslib.spa.gallery.preference
 
 import android.os.Bundle
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import androidx.compose.runtime.Composable
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
 
 private const val TITLE = "Category: Preference"
 
 object PreferenceMainPageProvider : SettingsPageProvider {
     override val name = "PreferenceMain"
-    private val owner = createSettingsPage()
 
-    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        return listOf(
-            PreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            SwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            MainSwitchPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            ListPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            TwoTargetSwitchPreferencePageProvider.buildInjectEntry()
-                .setLink(fromPage = owner).build(),
-            ZeroStatePreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            IntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            TopIntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-        )
-    }
-
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = owner)
-            .setUiLayoutFn {
-                Preference(object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                })
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        RegularScaffold(TITLE) {
+            Category {
+                PreferencePageProvider.Entry()
+                ListPreferencePageProvider.Entry()
             }
+            Category {
+                SwitchPreferencePageProvider.Entry()
+                MainSwitchPreferencePageProvider.Entry()
+                TwoTargetSwitchPreferencePageProvider.Entry()
+            }
+            Category {
+                ZeroStatePreferencePageProvider.Entry()
+                IntroPreferencePageProvider.Entry()
+                TopIntroPreferencePageProvider.Entry()
+            }
+        }
     }
 
-    override fun getTitle(arguments: Bundle?): String {
-        return TITLE
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
     }
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
deleted file mode 100644
index fc6f10f..0000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.gallery.preference
-
-import android.os.Bundle
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
-import androidx.compose.runtime.mutableStateOf
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.viewModelScope
-import androidx.lifecycle.viewmodel.compose.viewModel
-import com.android.settingslib.spa.framework.common.PageModel
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-
-private const val TAG = "PreferencePageModel"
-
-class PreferencePageModel : PageModel() {
-    companion object {
-        // Defines all the resources for this page.
-        // In real Settings App, resources data is defined in xml, rather than SPP.
-        const val PAGE_TITLE = "Sample Preference"
-        const val SIMPLE_PREFERENCE_TITLE = "Preference"
-        const val SIMPLE_PREFERENCE_SUMMARY = "Simple summary"
-        const val DISABLE_PREFERENCE_TITLE = "Disabled"
-        const val DISABLE_PREFERENCE_SUMMARY = "Disabled summary"
-        const val ASYNC_PREFERENCE_TITLE = "Async Preference"
-        const val ASYNC_PREFERENCE_SUMMARY = "Async summary"
-        const val MANUAL_UPDATE_PREFERENCE_TITLE = "Manual Updater"
-        const val AUTO_UPDATE_PREFERENCE_TITLE = "Auto Updater"
-        val SIMPLE_PREFERENCE_KEYWORDS = listOf("simple keyword1", "simple keyword2")
-
-        @Composable
-        fun create(): PreferencePageModel {
-            val pageModel: PreferencePageModel = viewModel()
-            pageModel.initOnce()
-            return pageModel
-        }
-    }
-
-    private val spaLogger = SpaEnvironmentFactory.instance.logger
-
-    val asyncSummary = mutableStateOf("(loading)")
-    val asyncEnable = mutableStateOf(false)
-
-    private val manualUpdater = mutableStateOf(0)
-
-    private val autoUpdater = object : MutableLiveData<String>(" ") {
-        private var tick = 0
-        private var updateJob: Job? = null
-        override fun onActive() {
-            spaLogger.message(TAG, "autoUpdater.active")
-            updateJob = viewModelScope.launch(Dispatchers.IO) {
-                while (true) {
-                    delay(1000L)
-                    tick++
-                    spaLogger.message(TAG, "autoUpdater.value $tick")
-                    postValue(tick.toString())
-                }
-            }
-        }
-
-        override fun onInactive() {
-            spaLogger.message(TAG, "autoUpdater.inactive")
-            updateJob?.cancel()
-        }
-    }
-
-    override fun initialize(arguments: Bundle?) {
-        spaLogger.message(TAG, "initialize with args " + arguments.toString())
-        viewModelScope.launch(Dispatchers.IO) {
-            // Loading your data here.
-            delay(2000L)
-            asyncSummary.value = ASYNC_PREFERENCE_SUMMARY
-            asyncEnable.value = true
-        }
-    }
-
-    fun getManualUpdaterSummary(): State<String> {
-        spaLogger.message(TAG, "getManualUpdaterSummary")
-        return derivedStateOf { manualUpdater.value.toString() }
-    }
-
-    fun manualUpdaterOnClick() {
-        spaLogger.message(TAG, "manualUpdaterOnClick")
-        manualUpdater.value = manualUpdater.value + 1
-    }
-
-    fun getAutoUpdaterSummary(): LiveData<String> {
-        spaLogger.message(TAG, "getAutoUpdaterSummary")
-        return autoUpdater
-    }
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
index 6d1d346..f7649b9 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageProvider.kt
@@ -18,187 +18,100 @@
 
 import android.os.Bundle
 import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.Autorenew
 import androidx.compose.material.icons.outlined.DisabledByDefault
-import androidx.compose.material.icons.outlined.TouchApp
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.produceState
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.EntryStatusData
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.gallery.R
-import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_TITLE
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.AUTO_UPDATE_PREFERENCE_TITLE
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_SUMMARY
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.DISABLE_PREFERENCE_TITLE
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.MANUAL_UPDATE_PREFERENCE_TITLE
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.PAGE_TITLE
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_KEYWORDS
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_SUMMARY
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_TITLE
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
 import com.android.settingslib.spa.widget.ui.SettingsIcon
-
-private const val TAG = "PreferencePage"
+import kotlinx.coroutines.delay
 
 object PreferencePageProvider : SettingsPageProvider {
-    // Defines all entry name in this page.
-    // Note that entry name would be used in log. DO NOT change it once it is set.
-    // One can still change the display name for better readability if necessary.
-    enum class EntryEnum(val displayName: String) {
-        SIMPLE_PREFERENCE("preference"),
-        SUMMARY_PREFERENCE("preference_with_summary"),
-        SINGLE_LINE_SUMMARY_PREFERENCE("preference_with_single_line_summary"),
-        DISABLED_PREFERENCE("preference_disable"),
-        ASYNC_SUMMARY_PREFERENCE("preference_with_async_summary"),
-        MANUAL_UPDATE_PREFERENCE("preference_actionable"),
-        AUTO_UPDATE_PREFERENCE("preference_auto_update"),
-    }
 
-    override val name = SettingsPageProviderEnum.PREFERENCE.name
-    override val displayName = SettingsPageProviderEnum.PREFERENCE.displayName
-    private val spaLogger = SpaEnvironmentFactory.instance.logger
-    private val owner = createSettingsPage()
+    override val name = "Preference"
+    private const val PAGE_TITLE = "Sample Preference"
 
-    private fun createEntry(entry: EntryEnum): SettingsEntryBuilder {
-        return SettingsEntryBuilder.create(owner, entry.name, entry.displayName)
-    }
-
-    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        val entryList = mutableListOf<SettingsEntry>()
-        entryList.add(
-            createEntry(EntryEnum.SIMPLE_PREFERENCE)
-                .setMacro {
-                    spaLogger.message(TAG, "create macro for ${EntryEnum.SIMPLE_PREFERENCE}")
-                    SimplePreferenceMacro(title = SIMPLE_PREFERENCE_TITLE)
-                }
-                .setStatusDataFn { EntryStatusData(isDisabled = false) }
-                .build()
-        )
-        entryList.add(
-            createEntry(EntryEnum.SUMMARY_PREFERENCE)
-                .setMacro {
-                    spaLogger.message(TAG, "create macro for ${EntryEnum.SUMMARY_PREFERENCE}")
-                    SimplePreferenceMacro(
-                        title = SIMPLE_PREFERENCE_TITLE,
-                        summary = SIMPLE_PREFERENCE_SUMMARY,
-                        searchKeywords = SIMPLE_PREFERENCE_KEYWORDS,
-                    )
-                }
-                .setStatusDataFn { EntryStatusData(isDisabled = true) }
-                .build()
-        )
-        entryList.add(singleLineSummaryEntry())
-        entryList.add(
-            createEntry(EntryEnum.DISABLED_PREFERENCE)
-                .setHasMutableStatus(true)
-                .setMacro {
-                    spaLogger.message(TAG, "create macro for ${EntryEnum.DISABLED_PREFERENCE}")
-                    SimplePreferenceMacro(
-                        title = DISABLE_PREFERENCE_TITLE,
-                        summary = DISABLE_PREFERENCE_SUMMARY,
-                        disabled = true,
-                        icon = Icons.Outlined.DisabledByDefault,
-                    )
-                }
-                .setStatusDataFn { EntryStatusData(isDisabled = true) }
-                .build()
-        )
-        entryList.add(
-            createEntry(EntryEnum.ASYNC_SUMMARY_PREFERENCE)
-                .setHasMutableStatus(true)
-                .setSearchDataFn {
-                    EntrySearchData(title = ASYNC_PREFERENCE_TITLE)
-                }
-                .setStatusDataFn { EntryStatusData(isDisabled = false) }
-                .setUiLayoutFn {
-                    val model = PreferencePageModel.create()
-                    Preference(
-                        object : PreferenceModel {
-                            override val title = ASYNC_PREFERENCE_TITLE
-                            override val summary = { model.asyncSummary.value }
-                            override val enabled = { model.asyncEnable.value }
-                        }
-                    )
-                }.build()
-        )
-        entryList.add(
-            createEntry(EntryEnum.MANUAL_UPDATE_PREFERENCE)
-                .setUiLayoutFn {
-                    val model = PreferencePageModel.create()
-                    val manualUpdaterSummary = remember { model.getManualUpdaterSummary() }
-                    Preference(
-                        object : PreferenceModel {
-                            override val title = MANUAL_UPDATE_PREFERENCE_TITLE
-                            override val summary = { manualUpdaterSummary.value }
-                            override val onClick = { model.manualUpdaterOnClick() }
-                            override val icon = @Composable {
-                                SettingsIcon(imageVector = Icons.Outlined.TouchApp)
-                            }
-                        }
-                    )
-                }.build()
-        )
-        entryList.add(
-            createEntry(EntryEnum.AUTO_UPDATE_PREFERENCE)
-                .setUiLayoutFn {
-                    val model = PreferencePageModel.create()
-                    val autoUpdaterSummary = remember {
-                        model.getAutoUpdaterSummary()
-                    }.observeAsState(" ")
-                    Preference(
-                        object : PreferenceModel {
-                            override val title = AUTO_UPDATE_PREFERENCE_TITLE
-                            override val summary = { autoUpdaterSummary.value }
-                            override val icon = @Composable {
-                                SettingsIcon(imageVector = Icons.Outlined.Autorenew)
-                            }
-                        }
-                    )
-                }.build()
-        )
-
-        return entryList
-    }
-
-    private fun singleLineSummaryEntry() = createEntry(EntryEnum.SINGLE_LINE_SUMMARY_PREFERENCE)
-        .setUiLayoutFn {
-            val summary = stringResource(R.string.single_line_summary_preference_summary)
-            Preference(
-                model = object : PreferenceModel {
-                    override val title: String =
-                        stringResource(R.string.single_line_summary_preference_title)
-                    override val summary = { summary }
-                },
-                singleLineSummary = true,
-            )
-        }
-        .build()
-
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = owner)
-            .setMacro {
-                spaLogger.message(TAG, "create macro for INJECT entry")
-                SimplePreferenceMacro(
-                    title = PAGE_TITLE,
-                    clickRoute = SettingsPageProviderEnum.PREFERENCE.name
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        RegularScaffold(PAGE_TITLE) {
+            Category {
+                Preference(object : PreferenceModel {
+                    override val title = "Preference"
+                })
+                Preference(object : PreferenceModel {
+                    override val title = "Preference"
+                    override val summary = { "Simple summary" }
+                })
+                val summary = stringResource(R.string.single_line_summary_preference_summary)
+                Preference(
+                    model = object : PreferenceModel {
+                        override val title =
+                            stringResource(R.string.single_line_summary_preference_title)
+                        override val summary = { summary }
+                    },
+                    singleLineSummary = true,
                 )
             }
+            Category {
+                Preference(object : PreferenceModel {
+                    override val title = "Disabled"
+                    override val summary = { "Disabled summary" }
+                    override val enabled = { false }
+                    override val icon = @Composable {
+                        SettingsIcon(imageVector = Icons.Outlined.DisabledByDefault)
+                    }
+                })
+            }
+            Category {
+                Preference(object : PreferenceModel {
+                    override val title = "Preference"
+                    val asyncSummary by produceState(initialValue = " ") {
+                        delay(1000L)
+                        value = "Async summary"
+                    }
+                    override val summary = { asyncSummary }
+                })
+
+                var count by remember { mutableIntStateOf(0) }
+                Preference(object : PreferenceModel {
+                    override val title = "Click me"
+                    override val summary = { count.toString() }
+                    override val onClick: (() -> Unit) = { count++ }
+                })
+
+                var ticks by remember { mutableIntStateOf(0) }
+                LaunchedEffect(ticks) {
+                    delay(1000L)
+                    ticks++
+                }
+                Preference(object : PreferenceModel {
+                    override val title = "Ticker"
+                    override val summary = { ticks.toString() }
+                })
+            }
+        }
     }
 
-    override fun getTitle(arguments: Bundle?): String {
-        return PAGE_TITLE
+    @Composable
+    fun Entry() {
+        Preference(model = object : PreferenceModel {
+            override val title = PAGE_TITLE
+            override val onClick = navigator(name)
+        })
     }
 }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePageProvider.kt
index f2225fa..9508d50 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePageProvider.kt
@@ -27,16 +27,15 @@
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.preference.SwitchPreference
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
 import com.android.settingslib.spa.widget.ui.SettingsIcon
 import kotlinx.coroutines.delay
 
@@ -44,56 +43,26 @@
 
 object SwitchPreferencePageProvider : SettingsPageProvider {
     override val name = "SwitchPreference"
-    private val owner = createSettingsPage()
 
-    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        val entryList = mutableListOf<SettingsEntry>()
-        entryList.add(
-            SettingsEntryBuilder.create( "SwitchPreference", owner)
-                .setUiLayoutFn {
-                    SampleSwitchPreference()
-                }.build()
-        )
-        entryList.add(
-            SettingsEntryBuilder.create( "SwitchPreference with summary", owner)
-                .setUiLayoutFn {
-                    SampleSwitchPreferenceWithSummary()
-                }.build()
-        )
-        entryList.add(
-            SettingsEntryBuilder.create( "SwitchPreference with async summary", owner)
-                .setUiLayoutFn {
-                    SampleSwitchPreferenceWithAsyncSummary()
-                }.build()
-        )
-        entryList.add(
-            SettingsEntryBuilder.create( "SwitchPreference not changeable", owner)
-                .setUiLayoutFn {
-                    SampleNotChangeableSwitchPreference()
-                }.build()
-        )
-        entryList.add(
-            SettingsEntryBuilder.create( "SwitchPreference with icon", owner)
-                .setUiLayoutFn {
-                    SampleSwitchPreferenceWithIcon()
-                }.build()
-        )
-
-        return entryList
-    }
-
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner)
-            .setUiLayoutFn {
-                Preference(object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                })
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        RegularScaffold(TITLE) {
+            Category {
+                SampleSwitchPreference()
+                SampleSwitchPreferenceWithSummary()
+                SampleSwitchPreferenceWithAsyncSummary()
+                SampleNotChangeableSwitchPreference()
+                SampleSwitchPreferenceWithIcon()
             }
+        }
     }
 
-    override fun getTitle(arguments: Bundle?): String {
-        return TITLE
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
     }
 }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
index b251266..ee08e30 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
@@ -50,15 +50,12 @@
         return entryList
     }
 
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
-            Preference(
-                object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                }
-            )
-        }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
     }
 
     override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePageProvider.kt
index 19de31d..1a89bb2 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePageProvider.kt
@@ -25,66 +25,40 @@
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
 import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.Category
 import kotlinx.coroutines.delay
 
 private const val TITLE = "Sample TwoTargetSwitchPreference"
 
 object TwoTargetSwitchPreferencePageProvider : SettingsPageProvider {
     override val name = "TwoTargetSwitchPreference"
-    private val owner = createSettingsPage()
 
-    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        val entryList = mutableListOf<SettingsEntry>()
-        entryList.add(
-            SettingsEntryBuilder.create( "TwoTargetSwitchPreference", owner)
-                .setUiLayoutFn {
-                    SampleTwoTargetSwitchPreference()
-                }.build()
-        )
-        entryList.add(
-            SettingsEntryBuilder.create( "TwoTargetSwitchPreference with summary", owner)
-                .setUiLayoutFn {
-                    SampleTwoTargetSwitchPreferenceWithSummary()
-                }.build()
-        )
-        entryList.add(
-            SettingsEntryBuilder.create( "TwoTargetSwitchPreference with async summary", owner)
-                .setUiLayoutFn {
-                    SampleTwoTargetSwitchPreferenceWithAsyncSummary()
-                }.build()
-        )
-        entryList.add(
-            SettingsEntryBuilder.create( "TwoTargetSwitchPreference not changeable", owner)
-                .setUiLayoutFn {
-                    SampleNotChangeableTwoTargetSwitchPreference()
-                }.build()
-        )
-
-        return entryList
-    }
-
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner)
-            .setUiLayoutFn {
-                Preference(object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                })
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        RegularScaffold(TITLE) {
+            Category {
+                SampleTwoTargetSwitchPreference()
+                SampleTwoTargetSwitchPreferenceWithSummary()
+                SampleTwoTargetSwitchPreferenceWithAsyncSummary()
+                SampleNotChangeableTwoTargetSwitchPreference()
             }
+        }
     }
 
-    override fun getTitle(arguments: Bundle?): String {
-        return TITLE
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
     }
 }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
index 4a9c5c8..04b5ceb 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
@@ -53,14 +53,12 @@
         return entryList
     }
 
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner)
-            .setUiLayoutFn {
-                Preference(object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                })
-            }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
     }
 
     override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt
index 66cc38f..c9a6557 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/PagerMainPageProvider.kt
@@ -17,7 +17,7 @@
 package com.android.settingslib.spa.gallery.scaffold
 
 import android.os.Bundle
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import androidx.compose.runtime.Composable
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
@@ -34,13 +34,13 @@
         ScrollablePagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
     )
 
-    fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
-        .setUiLayoutFn {
-            Preference(object : PreferenceModel {
-                override val title = TITLE
-                override val onClick = navigator(name)
-            })
-        }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
+    }
 
     override fun getTitle(arguments: Bundle?) = TITLE
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SearchScaffoldPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SearchScaffoldPageProvider.kt
index eac06e3..0d7cad10 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SearchScaffoldPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SearchScaffoldPageProvider.kt
@@ -18,9 +18,7 @@
 
 import android.os.Bundle
 import androidx.compose.runtime.Composable
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -32,15 +30,13 @@
 object SearchScaffoldPageProvider : SettingsPageProvider {
     override val name = "SearchScaffold"
 
-    private val owner = createSettingsPage()
-
-    fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
-        .setUiLayoutFn {
-            Preference(object : PreferenceModel {
-                override val title = TITLE
-                override val onClick = navigator(name)
-            })
-        }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
+    }
 
     @Composable
     override fun Page(arguments: Bundle?) {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt
index a0ab2ce..7b02fcb 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt
@@ -27,9 +27,7 @@
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.gallery.R
@@ -49,15 +47,13 @@
 object SuwScaffoldPageProvider : SettingsPageProvider {
     override val name = "SuwScaffold"
 
-    private val owner = createSettingsPage()
-
-    fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner)
-        .setUiLayoutFn {
-            Preference(object : PreferenceModel {
-                override val title = TITLE
-                override val onClick = navigator(name)
-            })
-        }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
+    }
 
     @Composable
     override fun Page(arguments: Bundle?) {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt
index aaeb22e..4d3a78a5 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPageProvider.kt
@@ -19,7 +19,6 @@
 import android.os.Bundle
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.EntrySearchData
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -38,17 +37,14 @@
     override val name = "Category"
     private val owner = createSettingsPage()
 
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner)
-            .setUiLayoutFn {
-                Preference(
-                    object : PreferenceModel {
-                        override val title = TITLE
-                        override val onClick = navigator(name)
-                    }
-                )
+    @Composable
+    fun Entry() {
+        Preference(
+            object : PreferenceModel {
+                override val title = TITLE
+                override val onClick = navigator(name)
             }
-            .setSearchDataFn { EntrySearchData(title = TITLE) }
+        )
     }
 
     override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt
index f897d8c..e919129 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CopyablePageProvider.kt
@@ -21,10 +21,7 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.widget.preference.Preference
@@ -37,17 +34,12 @@
 object CopyablePageProvider : SettingsPageProvider {
     override val name = "Copyable"
 
-    private val owner = createSettingsPage()
-
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner)
-            .setUiLayoutFn {
-                Preference(object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                })
-            }
-            .setSearchDataFn { EntrySearchData(title = TITLE) }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
     }
 
     @Composable
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPageProvider.kt
index 5c5c504..7a4b632 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPageProvider.kt
@@ -23,9 +23,7 @@
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.tooling.preview.Preview
-import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
-import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.preference.Preference
@@ -39,14 +37,12 @@
 object SpinnerPageProvider : SettingsPageProvider {
     override val name = "Spinner"
 
-    fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = createSettingsPage())
-            .setUiLayoutFn {
-                Preference(object : PreferenceModel {
-                    override val title = TITLE
-                    override val onClick = navigator(name)
-                })
-            }
+    @Composable
+    fun Entry() {
+        Preference(object : PreferenceModel {
+            override val title = TITLE
+            override val onClick = navigator(name)
+        })
     }
 
     override fun getTitle(arguments: Bundle?): String {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/banner/SettingsBanner.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/banner/SettingsBanner.kt
index 185fd29..38707b0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/banner/SettingsBanner.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/banner/SettingsBanner.kt
@@ -57,6 +57,7 @@
 import com.android.settingslib.spa.framework.theme.SettingsShape.CornerExtraSmall
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
+import com.android.settingslib.spa.framework.theme.toSemiBoldWeight
 import com.android.settingslib.spa.widget.ui.SettingsBody
 import com.android.settingslib.spa.widget.ui.SettingsTitle
 
@@ -159,7 +160,9 @@
 @Composable
 fun BannerTitleHeader(title: String, onDismiss: (() -> Unit)? = null) {
     Row(Modifier.fillMaxWidth()) {
-        Box(modifier = Modifier.weight(1f)) { SettingsTitle(title) }
+        Box(modifier = Modifier.weight(1f)) {
+            Text(text = title, style = MaterialTheme.typography.titleMedium.toSemiBoldWeight())
+        }
         Spacer(modifier = Modifier.padding(SettingsDimension.paddingSmall))
         DismissButton(onDismiss)
     }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
index 5bb57b8..203a8bd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt
@@ -56,6 +56,7 @@
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.framework.theme.divider
 import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled
+import com.android.settingslib.spa.framework.theme.toSemiBoldWeight
 
 data class ActionButton(
     val text: String,
@@ -129,7 +130,7 @@
                 Text(
                     text = actionButton.text,
                     textAlign = TextAlign.Center,
-                    style = MaterialTheme.typography.labelMedium,
+                    style = MaterialTheme.typography.labelLarge.toSemiBoldWeight(),
                 )
             }
         }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
index 265864e..490936f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt
@@ -26,6 +26,7 @@
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
@@ -99,7 +100,16 @@
             dismissButton?.let {
                 { if (isSpaExpressiveEnabled) DismissButton(it) else Button(it) }
             },
-        title = title?.let { { CenterRow { Text(it) } } },
+        title =
+            title?.let {
+                {
+                    CenterRow {
+                        if (isSpaExpressiveEnabled)
+                            Text(it, style = MaterialTheme.typography.bodyLarge)
+                        else Text(it)
+                    }
+                }
+            },
         text =
             text?.let {
                 { CenterRow { Column(Modifier.verticalScroll(rememberScrollState())) { text() } } }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
index 22a5755..7707376 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
@@ -36,6 +36,7 @@
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.toSemiBoldWeight
 
 @Composable
 fun IntroPreference(
@@ -112,7 +113,7 @@
         Text(
             text = title,
             textAlign = TextAlign.Center,
-            style = MaterialTheme.typography.titleLarge,
+            style = MaterialTheme.typography.titleLarge.toSemiBoldWeight(),
             color = MaterialTheme.colorScheme.onSurface,
         )
     }
@@ -126,7 +127,7 @@
             Text(
                 text = description,
                 textAlign = TextAlign.Center,
-                style = MaterialTheme.typography.titleMedium,
+                style = MaterialTheme.typography.bodyLarge,
                 color = MaterialTheme.colorScheme.onSurfaceVariant,
                 modifier = Modifier.padding(top = SettingsDimension.paddingExtraSmall),
             )
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
index 3f2e772..b771f36 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
@@ -47,6 +47,7 @@
 import androidx.graphics.shapes.RoundedPolygon
 import androidx.graphics.shapes.star
 import androidx.graphics.shapes.toPath
+import com.android.settingslib.spa.framework.theme.toSemiBoldWeight
 
 @Composable
 fun ZeroStatePreference(icon: ImageVector, text: String? = null, description: String? = null) {
@@ -80,7 +81,7 @@
             Text(
                 text = text,
                 textAlign = TextAlign.Center,
-                style = MaterialTheme.typography.titleMedium,
+                style = MaterialTheme.typography.titleMedium.toSemiBoldWeight(),
                 color = MaterialTheme.colorScheme.onSurfaceVariant,
                 modifier = Modifier.padding(top = 24.dp),
             )
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
index 5656f38..3627669 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
@@ -63,7 +63,8 @@
                                 },
                             moreSettingsHelpItem = readParcelable(
                                 DeviceSettingItem::class.java.classLoader
-                            )
+                            ),
+                            extras = readBundle((Bundle::class.java.classLoader)) ?: Bundle.EMPTY,
                         )
                     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
index 7f17293..ebaad34 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
@@ -86,6 +86,7 @@
         assertThat(fromParcel.moreSettingsHelpItem?.packageName).isEqualTo("package_name_2")
         assertThat(fromParcel.moreSettingsHelpItem?.className).isEqualTo("class_name_2")
         assertThat(fromParcel.moreSettingsHelpItem?.intentAction).isEqualTo("intent_action_2")
+        assertThat(fromParcel.extras.getString("key1")).isEqualTo("value1")
     }
 
     private fun writeAndRead(item: DeviceSettingsConfig): DeviceSettingsConfig {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppPreferenceTest.java
deleted file mode 100644
index 6c8fd50..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AppPreferenceTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.widget;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.view.View;
-
-import androidx.preference.PreferenceViewHolder;
-
-import com.android.settingslib.widget.preference.app.R;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-@RunWith(RobolectricTestRunner.class)
-public class AppPreferenceTest {
-
-    private Context mContext;
-    private View mRootView;
-    private AppPreference mPref;
-    private PreferenceViewHolder mHolder;
-
-    @Before
-    public void setUp() {
-        mContext = RuntimeEnvironment.application;
-        mRootView = View.inflate(mContext, R.layout.preference_app, null /* parent */);
-        mHolder = PreferenceViewHolder.createInstanceForTests(mRootView);
-        mPref = new AppPreference(mContext);
-    }
-
-    @Test
-    public void setProgress_showProgress() {
-        mPref.setProgress(1);
-        mPref.onBindViewHolder(mHolder);
-
-        assertThat(mHolder.findViewById(android.R.id.progress).getVisibility())
-                .isEqualTo(View.VISIBLE);
-    }
-
-    @Test
-    public void foobar_testName() {
-        float iconSize = mContext.getResources().getDimension(com.android.settingslib.widget.theme.R.dimen.secondary_app_icon_size);
-        assertThat(Float.floatToIntBits(iconSize)).isEqualTo(Float.floatToIntBits(32));
-    }
-}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a9d4c89..cb1411b 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -559,6 +559,13 @@
 }
 
 flag {
+    name: "volume_redesign"
+    namespace: "systemui"
+    description: "Enables Volume BC25 visuals update"
+    bug: "368308908"
+}
+
+flag {
     name: "clipboard_shared_transitions"
     namespace: "systemui"
     description: "Show shared transitions from clipboard"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
new file mode 100644
index 0000000..4bbdfa4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.viewmodel
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.testing.TestLifecycleOwner
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+
+@OptIn(ExperimentalCoroutinesApi::class)
+abstract class AbstractQSFragmentComposeViewModelTest : SysuiTestCase() {
+    protected val kosmos = testKosmos()
+
+    protected val lifecycleOwner =
+        TestLifecycleOwner(
+            initialState = Lifecycle.State.CREATED,
+            coroutineDispatcher = kosmos.testDispatcher,
+        )
+
+    protected val underTest by lazy {
+        kosmos.qsFragmentComposeViewModelFactory.create(lifecycleOwner.lifecycleScope)
+    }
+
+    @Before
+    fun setUp() {
+        Dispatchers.setMain(kosmos.testDispatcher)
+    }
+
+    @After
+    fun teardown() {
+        Dispatchers.resetMain()
+    }
+
+    protected inline fun TestScope.testWithinLifecycle(
+        crossinline block: suspend TestScope.() -> TestResult
+    ): TestResult {
+        return runTest {
+            lifecycleOwner.setCurrentState(Lifecycle.State.RESUMED)
+            lifecycleOwner.lifecycleScope.launch { underTest.activate() }
+            block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) }
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt
new file mode 100644
index 0000000..57a9377
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelForceQSTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.viewmodel
+
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.sysuiStatusBarStateController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@SmallTest
+@RunWith(Parameterized::class)
+@RunWithLooper
+class QSFragmentComposeViewModelForceQSTest(private val testData: TestData) :
+    AbstractQSFragmentComposeViewModelTest() {
+
+    @Test
+    fun forceQs_orRealExpansion() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                val expansionState by collectLastValue(underTest.expansionState)
+
+                with(testData) {
+                    sysuiStatusBarStateController.setState(statusBarState)
+                    underTest.isQSExpanded = expanded
+                    underTest.isStackScrollerOverscrolling = stackScrollerOverScrolling
+                    fakeDeviceEntryRepository.setBypassEnabled(bypassEnabled)
+                    underTest.isTransitioningToFullShade = transitioningToFullShade
+                    underTest.isInSplitShade = inSplitShade
+
+                    underTest.qsExpansionValue = EXPANSION
+                    assertThat(expansionState!!.progress)
+                        .isEqualTo(if (expectedForceQS) 1f else EXPANSION)
+                }
+            }
+        }
+
+    data class TestData(
+        val statusBarState: Int,
+        val expanded: Boolean,
+        val stackScrollerOverScrolling: Boolean,
+        val bypassEnabled: Boolean,
+        val transitioningToFullShade: Boolean,
+        val inSplitShade: Boolean,
+    ) {
+        private val inKeyguard = statusBarState == StatusBarState.KEYGUARD
+
+        private val showCollapsedOnKeyguard =
+            bypassEnabled || (transitioningToFullShade && !inSplitShade)
+
+        val expectedForceQS =
+            (expanded || stackScrollerOverScrolling) && (inKeyguard && !showCollapsedOnKeyguard)
+    }
+
+    companion object {
+        private const val EXPANSION = 0.3f
+
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun createTestData(): List<TestData> {
+            return statusBarStates.flatMap { statusBarState ->
+                (0u..31u).map { bitfield ->
+                    TestData(
+                        statusBarState,
+                        expanded = (bitfield or 1u) == 1u,
+                        stackScrollerOverScrolling = (bitfield or 2u) == 1u,
+                        bypassEnabled = (bitfield or 4u) == 1u,
+                        transitioningToFullShade = (bitfield or 8u) == 1u,
+                        inSplitShade = (bitfield or 16u) == 1u,
+                    )
+                }
+            }
+        }
+
+        private val statusBarStates =
+            setOf(StatusBarState.SHADE, StatusBarState.KEYGUARD, StatusBarState.SHADE_LOCKED)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
index 6f20e70..c19e4b8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -19,64 +19,28 @@
 import android.app.StatusBarManager
 import android.content.testableContext
 import android.testing.TestableLooper.RunWithLooper
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.qs.fgsManagerController
+import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
 import com.android.systemui.res.R
 import com.android.systemui.shade.largeScreenHeaderHelper
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
 import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
 import com.android.systemui.statusbar.sysuiStatusBarStateController
-import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestResult
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.resetMain
 import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import kotlinx.coroutines.test.setMain
-import org.junit.After
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper
-@OptIn(ExperimentalCoroutinesApi::class)
-class QSFragmentComposeViewModelTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-
-    private val lifecycleOwner =
-        TestLifecycleOwner(
-            initialState = Lifecycle.State.CREATED,
-            coroutineDispatcher = kosmos.testDispatcher,
-        )
-
-    private val underTest by lazy {
-        kosmos.qsFragmentComposeViewModelFactory.create(lifecycleOwner.lifecycleScope)
-    }
-
-    @Before
-    fun setUp() {
-        Dispatchers.setMain(kosmos.testDispatcher)
-    }
-
-    @After
-    fun teardown() {
-        Dispatchers.resetMain()
-    }
+class QSFragmentComposeViewModelTest : AbstractQSFragmentComposeViewModelTest() {
 
     @Test
     fun qsExpansionValueChanges_correctExpansionState() =
@@ -205,16 +169,30 @@
             }
         }
 
-    private inline fun TestScope.testWithinLifecycle(
-        crossinline block: suspend TestScope.() -> TestResult
-    ): TestResult {
-        return runTest {
-            lifecycleOwner.setCurrentState(Lifecycle.State.RESUMED)
-            block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) }
+    @Test
+    fun squishinessInExpansion_setInInteractor() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                val squishiness by collectLastValue(tileSquishinessInteractor.squishiness)
+
+                underTest.squishinessFractionValue = 0.3f
+                assertThat(squishiness).isWithin(epsilon).of(0.3f.constrainSquishiness())
+
+                underTest.squishinessFractionValue = 0f
+                assertThat(squishiness).isWithin(epsilon).of(0f.constrainSquishiness())
+
+                underTest.squishinessFractionValue = 1f
+                assertThat(squishiness).isWithin(epsilon).of(1f.constrainSquishiness())
+            }
         }
-    }
 
     companion object {
         private const val QS_DISABLE_FLAG = StatusBarManager.DISABLE2_QUICK_SETTINGS
+
+        private fun Float.constrainSquishiness(): Float {
+            return (0.1f + this * 0.9f).coerceIn(0f, 1f)
+        }
+
+        private const val epsilon = 0.001f
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
index 9e90090..a9a527f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayoutTest.kt
@@ -22,10 +22,8 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.qs.panels.data.repository.DefaultLargeTilesRepository
 import com.android.systemui.qs.panels.data.repository.defaultLargeTilesRepository
-import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
+import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout
 import com.android.systemui.qs.panels.ui.viewmodel.MockTileViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -44,8 +42,7 @@
                 }
         }
 
-    private val underTest =
-        with(kosmos) { InfiniteGridLayout(iconTilesViewModel, fixedColumnsSizeViewModel) }
+    private val underTest = kosmos.infiniteGridLayout
 
     @Test
     fun correctPagination_underOnePage_sameOrder() =
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 ba0d938..66ac01a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -180,6 +180,7 @@
         qqsMediaHost.init(MediaHierarchyManager.LOCATION_QQS)
         qsMediaHost.init(MediaHierarchyManager.LOCATION_QS)
         setListenerCollections()
+        lifecycleScope.launch { viewModel.activate() }
     }
 
     override fun onCreateView(
@@ -331,7 +332,7 @@
     }
 
     override fun setOverscrolling(overscrolling: Boolean) {
-        viewModel.stackScrollerOverscrollingValue = overscrolling
+        viewModel.isStackScrollerOverscrolling = overscrolling
     }
 
     override fun setExpanded(qsExpanded: Boolean) {
@@ -410,11 +411,11 @@
         qsTransitionFraction: Float,
         qsSquishinessFraction: Float,
     ) {
-        super.setTransitionToFullShadeProgress(
-            isTransitioningToFullShade,
-            qsTransitionFraction,
-            qsSquishinessFraction,
-        )
+        viewModel.isTransitioningToFullShade = isTransitioningToFullShade
+        viewModel.lockscreenToShadeProgressValue = qsTransitionFraction
+        if (isTransitioningToFullShade) {
+            viewModel.squishinessFractionValue = qsSquishinessFraction
+        }
     }
 
     override fun setFancyClipping(
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 7300ee1..2d4e358 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
@@ -24,16 +24,19 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel.QSExpansionState
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
 import com.android.systemui.shade.LargeScreenHeaderHelper
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
-import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.util.LargeScreenUtils
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.printSection
@@ -50,6 +53,7 @@
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
@@ -61,13 +65,14 @@
     private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
     private val footerActionsController: FooterActionsController,
     private val sysuiStatusBarStateController: SysuiStatusBarStateController,
-    private val keyguardBypassController: KeyguardBypassController,
+    private val deviceEntryInteractor: DeviceEntryInteractor,
     private val disableFlagsRepository: DisableFlagsRepository,
     private val largeScreenShadeInterpolator: LargeScreenShadeInterpolator,
     private val configurationInteractor: ConfigurationInteractor,
     private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
+    private val squishinessInteractor: TileSquishinessInteractor,
     @Assisted private val lifecycleScope: LifecycleCoroutineScope,
-) : Dumpable {
+) : Dumpable, ExclusiveActivatable() {
     val footerActionsViewModel =
         footerActionsViewModelFactory.create(lifecycleScope).also {
             lifecycleScope.launch { footerActionsController.init() }
@@ -110,7 +115,7 @@
             _panelFraction.value = value
         }
 
-    private val _squishinessFraction = MutableStateFlow(0f)
+    private val _squishinessFraction = MutableStateFlow(1f)
     var squishinessFractionValue: Float
         get() = _squishinessFraction.value
         set(value) {
@@ -131,7 +136,7 @@
     private val _headerAnimating = MutableStateFlow(false)
 
     private val _stackScrollerOverscrolling = MutableStateFlow(false)
-    var stackScrollerOverscrollingValue: Boolean
+    var isStackScrollerOverscrolling: Boolean
         get() = _stackScrollerOverscrolling.value
         set(value) {
             _stackScrollerOverscrolling.value = value
@@ -150,8 +155,6 @@
                 disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled(),
             )
 
-    private val _showCollapsedOnKeyguard = MutableStateFlow(false)
-
     private val _keyguardAndExpanded = MutableStateFlow(false)
 
     /**
@@ -177,21 +180,65 @@
 
                 awaitClose { sysuiStatusBarStateController.removeCallback(callback) }
             }
+            .onStart { emit(sysuiStatusBarStateController.state) }
             .stateIn(
                 lifecycleScope,
                 SharingStarted.WhileSubscribed(),
                 sysuiStatusBarStateController.state,
             )
 
+    private val isKeyguardState =
+        statusBarState
+            .map { it == StatusBarState.KEYGUARD }
+            .stateIn(
+                lifecycleScope,
+                SharingStarted.WhileSubscribed(),
+                statusBarState.value == StatusBarState.KEYGUARD,
+            )
+
     private val _viewHeight = MutableStateFlow(0)
 
     private val _headerTranslation = MutableStateFlow(0f)
 
     private val _inSplitShade = MutableStateFlow(false)
+    var isInSplitShade: Boolean
+        get() = _inSplitShade.value
+        set(value) {
+            _inSplitShade.value = value
+        }
 
     private val _transitioningToFullShade = MutableStateFlow(false)
+    var isTransitioningToFullShade: Boolean
+        get() = _transitioningToFullShade.value
+        set(value) {
+            _transitioningToFullShade.value = value
+        }
 
-    private val _lockscreenToShadeProgress = MutableStateFlow(false)
+    private val isBypassEnabled = deviceEntryInteractor.isBypassEnabled
+
+    private val showCollapsedOnKeyguard =
+        combine(
+                isBypassEnabled,
+                _transitioningToFullShade,
+                _inSplitShade,
+                ::calculateShowCollapsedOnKeyguard,
+            )
+            .stateIn(
+                lifecycleScope,
+                SharingStarted.WhileSubscribed(),
+                calculateShowCollapsedOnKeyguard(
+                    isBypassEnabled.value,
+                    isTransitioningToFullShade,
+                    isInSplitShade,
+                ),
+            )
+
+    private val _lockscreenToShadeProgress = MutableStateFlow(0.0f)
+    var lockscreenToShadeProgressValue: Float
+        get() = _lockscreenToShadeProgress.value
+        set(value) {
+            _lockscreenToShadeProgress.value = value
+        }
 
     private val _overscrolling = MutableStateFlow(false)
 
@@ -212,12 +259,32 @@
             _heightOverride.value = value
         }
 
+    private val forceQS =
+        combine(
+                _qsExpanded,
+                _stackScrollerOverscrolling,
+                isKeyguardState,
+                showCollapsedOnKeyguard,
+                ::calculateForceQs,
+            )
+            .stateIn(
+                lifecycleScope,
+                SharingStarted.WhileSubscribed(),
+                calculateForceQs(
+                    isQSExpanded,
+                    isStackScrollerOverscrolling,
+                    isKeyguardState.value,
+                    showCollapsedOnKeyguard.value,
+                ),
+            )
+
     val expansionState: StateFlow<QSExpansionState> =
-        combine(_stackScrollerOverscrolling, _qsExpanded, _qsExpansion) { args: Array<Any> ->
-                val expansion = args[2] as Float
-                QSExpansionState(expansion.coerceIn(0f, 1f))
-            }
-            .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), QSExpansionState(0f))
+        combine(_qsExpansion, forceQS, ::calculateExpansionState)
+            .stateIn(
+                lifecycleScope,
+                SharingStarted.WhileSubscribed(),
+                calculateExpansionState(_qsExpansion.value, forceQS.value),
+            )
 
     /**
      * Accessibility action for collapsing/expanding QS. The provided runnable is responsible for
@@ -225,6 +292,16 @@
      */
     var collapseExpandAccessibilityAction: Runnable? = null
 
+    override suspend fun onActivated(): Nothing {
+        hydrateSquishinessInteractor()
+    }
+
+    private suspend fun hydrateSquishinessInteractor(): Nothing {
+        _squishinessFraction.collect {
+            squishinessInteractor.setSquishinessValue(it.constrainSquishiness())
+        }
+    }
+
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         pw.asIndenting().run {
             printSection("Quick Settings state") {
@@ -238,13 +315,17 @@
                 println("panelExpansionFraction", panelExpansionFractionValue)
                 println("squishinessFraction", squishinessFractionValue)
                 println("expansionState", expansionState.value)
+                println("forceQS", forceQS.value)
             }
             printSection("Shade state") {
-                println("stackOverscrolling", stackScrollerOverscrollingValue)
+                println("stackOverscrolling", isStackScrollerOverscrolling)
                 println("statusBarState", StatusBarState.toString(statusBarState.value))
+                println("isKeyguardState", isKeyguardState.value)
                 println("isSmallScreen", isSmallScreenValue)
                 println("heightOverride", "${heightOverrideValue}px")
                 println("qqsHeaderHeight", "${qqsHeaderHeight.value}px")
+                println("isSplitShade", isInSplitShade)
+                println("showCollapsedOnKeyguard", showCollapsedOnKeyguard.value)
             }
         }
     }
@@ -257,3 +338,35 @@
     // In the future, this will have other relevant elements like squishiness.
     data class QSExpansionState(@FloatRange(0.0, 1.0) val progress: Float)
 }
+
+private fun Float.constrainSquishiness(): Float {
+    return (0.1f + this * 0.9f).coerceIn(0f, 1f)
+}
+
+// Helper methods for combining flows.
+
+private fun calculateExpansionState(expansion: Float, forceQs: Boolean): QSExpansionState {
+    return if (forceQs) {
+        QSExpansionState(1f)
+    } else {
+        QSExpansionState(expansion.coerceIn(0f, 1f))
+    }
+}
+
+private fun calculateForceQs(
+    isQSExpanded: Boolean,
+    isStackOverScrolling: Boolean,
+    isKeyguardShowing: Boolean,
+    shouldShowCollapsedOnKeyguard: Boolean,
+): Boolean {
+    return (isQSExpanded || isStackOverScrolling) &&
+        (isKeyguardShowing && !shouldShowCollapsedOnKeyguard)
+}
+
+private fun calculateShowCollapsedOnKeyguard(
+    isBypassEnabled: Boolean,
+    isTransitioningToFullShade: Boolean,
+    isInSplitShade: Boolean,
+): Boolean {
+    return isBypassEnabled || (isTransitioningToFullShade && !isInSplitShade)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepository.kt
new file mode 100644
index 0000000..76ba9af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepository.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class TileSquishinessRepository @Inject constructor() {
+    private val _squishiness = MutableStateFlow(1f)
+    val squishiness = _squishiness.asStateFlow()
+
+    fun setSquishinessValue(value: Float) {
+        _squishiness.value = value
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractor.kt
new file mode 100644
index 0000000..4fdbc76
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractor.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.panels.data.repository.TileSquishinessRepository
+import javax.inject.Inject
+
+@SysUISingleton
+class TileSquishinessInteractor
+@Inject
+constructor(private val repository: TileSquishinessRepository) {
+    val squishiness = repository.squishiness
+
+    fun setSquishinessValue(value: Float) {
+        repository.setSquishinessValue(value)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index 8998a7f..a645b51 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -41,6 +41,7 @@
     val sizedTiles by
         viewModel.tileViewModels.collectAsStateWithLifecycle(initialValue = emptyList())
     val tiles = sizedTiles.fastMap { it.tile }
+    val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle()
 
     DisposableEffect(tiles) {
         val token = Any()
@@ -62,6 +63,7 @@
                 tile = it.tile,
                 iconOnly = it.isIcon,
                 modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)),
+                squishiness = { squishiness },
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 8c2fb25..bf4c113 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -73,6 +73,7 @@
     secondaryLabel: String?,
     icon: Icon,
     colors: TileColors,
+    squishiness: () -> Float,
     accessibilityUiState: AccessibilityUiState? = null,
     toggleClickSupported: Boolean = false,
     iconShape: Shape = RoundedCornerShape(CommonTileDefaults.InactiveCornerRadius),
@@ -89,6 +90,7 @@
             modifier =
                 Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClickSupported) {
                     Modifier.clip(iconShape)
+                        .verticalSquish(squishiness)
                         .background(colors.iconBackground, { 1f })
                         .combinedClickable(
                             onClick = onClick,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index e6edba5..3ba49ad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.FixedColumnsSizeViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.TileSquishinessViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.shared.ui.ElementKeys.toElementKey
@@ -45,6 +46,7 @@
 constructor(
     private val iconTilesViewModel: IconTilesViewModel,
     private val gridSizeViewModel: FixedColumnsSizeViewModel,
+    private val squishinessViewModel: TileSquishinessViewModel,
 ) : PaginatableGridLayout {
 
     @Composable
@@ -60,6 +62,7 @@
         }
         val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
         val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) }
+        val squishiness by squishinessViewModel.squishiness.collectAsStateWithLifecycle()
 
         VerticalSpannedGrid(
             columns = columns,
@@ -72,6 +75,7 @@
                 tile = it.tile,
                 iconOnly = iconTilesViewModel.isIconTile(it.tile.spec),
                 modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)),
+                squishiness = { squishiness },
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt
new file mode 100644
index 0000000..ada1ef4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/SquishTile.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.compose.infinitegrid
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import kotlin.math.roundToInt
+
+/**
+ * Modifier to squish the vertical bounds of a composable (usually a QS tile).
+ *
+ * It will squish the vertical bounds of the inner composable node by the value returned by
+ * [squishiness] on the measure/layout pass.
+ *
+ * The squished composable will be center aligned.
+ */
+fun Modifier.verticalSquish(squishiness: () -> Float): Modifier {
+    return layout { measurable, constraints ->
+        val placeable = measurable.measure(constraints)
+        val actualHeight = placeable.height
+        val squishedHeight = actualHeight * squishiness()
+        // Center the content by moving it UP (squishedHeight < actualHeight)
+        val scroll = (squishedHeight - actualHeight) / 2
+
+        layout(placeable.width, squishedHeight.roundToInt()) {
+            placeable.place(0, scroll.roundToInt())
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index afcbed6d..4bd5b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -98,7 +98,12 @@
 }
 
 @Composable
-fun Tile(tile: TileViewModel, iconOnly: Boolean, modifier: Modifier) {
+fun Tile(
+    tile: TileViewModel,
+    iconOnly: Boolean,
+    squishiness: () -> Float,
+    modifier: Modifier = Modifier,
+) {
     val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
     val resources = resources()
     val uiState = remember(state, resources) { state.toUiState(resources) }
@@ -119,6 +124,7 @@
         onClick = tile::onClick,
         onLongClick = tile::onLongClick,
         uiState = uiState,
+        squishiness = squishiness,
         modifier = modifier,
     ) { expandable ->
         val icon = getTileIcon(icon = uiState.icon)
@@ -144,6 +150,7 @@
                 },
                 onLongClick = { tile.onLongClick(expandable) },
                 accessibilityUiState = uiState.accessibilityUiState,
+                squishiness = squishiness,
             )
         }
     }
@@ -155,12 +162,17 @@
     shape: Shape,
     iconOnly: Boolean,
     uiState: TileUiState,
+    squishiness: () -> Float,
     modifier: Modifier = Modifier,
     onClick: (Expandable) -> Unit = {},
     onLongClick: (Expandable) -> Unit = {},
     content: @Composable BoxScope.(Expandable) -> Unit,
 ) {
-    Expandable(color = color, shape = shape, modifier = modifier.clip(shape)) {
+    Expandable(
+        color = color,
+        shape = shape,
+        modifier = modifier.clip(shape).verticalSquish(squishiness),
+    ) {
         val longPressLabel = longPressLabel()
         Box(
             modifier =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
index eee905f..88e3019 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
@@ -42,6 +42,7 @@
     tilesInteractor: CurrentTilesInteractor,
     fixedColumnsSizeViewModel: FixedColumnsSizeViewModel,
     quickQuickSettingsRowInteractor: QuickQuickSettingsRowInteractor,
+    val squishinessViewModel: TileSquishinessViewModel,
     private val iconTilesViewModel: IconTilesViewModel,
     @Application private val applicationScope: CoroutineScope,
 ) {
@@ -52,7 +53,7 @@
         quickQuickSettingsRowInteractor.rows.stateIn(
             applicationScope,
             SharingStarted.WhileSubscribed(),
-            quickQuickSettingsRowInteractor.defaultRows
+            quickQuickSettingsRowInteractor.defaultRows,
         )
 
     val tileViewModels: StateFlow<List<SizedTile<TileViewModel>>> =
@@ -60,12 +61,7 @@
             .flatMapLatest { columns ->
                 tilesInteractor.currentTiles.combine(rows, ::Pair).mapLatest { (tiles, rows) ->
                     tiles
-                        .map {
-                            SizedTileImpl(
-                                TileViewModel(it.tile, it.spec),
-                                it.spec.width,
-                            )
-                        }
+                        .map { SizedTileImpl(TileViewModel(it.tile, it.spec), it.spec.width) }
                         .let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
                 }
             }
@@ -73,15 +69,10 @@
                 applicationScope,
                 SharingStarted.WhileSubscribed(),
                 tilesInteractor.currentTiles.value
-                    .map {
-                        SizedTileImpl(
-                            TileViewModel(it.tile, it.spec),
-                            it.spec.width,
-                        )
-                    }
+                    .map { SizedTileImpl(TileViewModel(it.tile, it.spec), it.spec.width) }
                     .let {
                         splitInRowsSequence(it, columns.value).take(rows.value).toList().flatten()
-                    }
+                    },
             )
 
     private val TileSpec.width: Int
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModel.kt
new file mode 100644
index 0000000..0c4d5de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.viewmodel
+
+import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor
+import javax.inject.Inject
+
+/** View model to track the squishiness of tiles. */
+class TileSquishinessViewModel
+@Inject
+constructor(tileSquishinessInteractor: TileSquishinessInteractor) {
+    val squishiness = tileSquishinessInteractor.squishiness
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
index da29b0f..ec5ebc36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt
@@ -43,7 +43,7 @@
         entry: NotificationEntry,
         builder: Notification.Builder,
         systemUIContext: Context,
-        packageContext: Context
+        packageContext: Context,
     ): RichOngoingContentModel?
 }
 
@@ -52,7 +52,7 @@
         entry: NotificationEntry,
         builder: Notification.Builder,
         systemUIContext: Context,
-        packageContext: Context
+        packageContext: Context,
     ): RichOngoingContentModel? = null
 }
 
@@ -68,7 +68,7 @@
         entry: NotificationEntry,
         builder: Notification.Builder,
         systemUIContext: Context,
-        packageContext: Context
+        packageContext: Context,
     ): RichOngoingContentModel? {
         val sbn = entry.sbn
         val notification = sbn.notification
@@ -89,7 +89,7 @@
                         null
                     }
                 }
-            } else if (builder.style is Notification.EnRouteStyle) {
+            } else if (builder.style is Notification.ProgressStyle) {
                 parseEnRouteNotification(notification, icon)
             } else null
         } catch (e: Exception) {
@@ -104,7 +104,7 @@
      */
     private fun parseTimerNotification(
         notification: Notification,
-        icon: IconModel
+        icon: IconModel,
     ): TimerContentModel {
         // sortKey=1 0|↺7|RUNNING|▶16:21:58.523|Σ0:05:00|Δ0:00:03|⏳0:04:57
         // sortKey=1 0|↺7|PAUSED|Σ0:05:00|Δ0:04:54|⏳0:00:06
@@ -132,7 +132,7 @@
                             resumeIntent = notification.findStartIntent(),
                             addMinuteAction = notification.findAddMinuteAction(),
                             resetAction = notification.findResetAction(),
-                        )
+                        ),
                 )
             }
             "RUNNING" -> {
@@ -149,7 +149,7 @@
                             pauseIntent = notification.findPauseIntent(),
                             addMinuteAction = notification.findAddMinuteAction(),
                             resetAction = notification.findResetAction(),
-                        )
+                        ),
                 )
             }
             else -> error("unknown state ($state) in sortKey=$sortKey")
@@ -192,7 +192,7 @@
         val localDateTime =
             LocalDateTime.of(
                 LocalDate.now(),
-                LocalTime.of(hour.toInt(), minute.toInt(), second.toInt(), millis.toInt() * 1000000)
+                LocalTime.of(hour.toInt(), minute.toInt(), second.toInt(), millis.toInt() * 1000000),
             )
         val offset = ZoneId.systemDefault().rules.getOffset(localDateTime)
         return localDateTime.toInstant(offset).toEpochMilli()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index ee96195..93db2db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -20,6 +20,7 @@
 import static android.service.notification.NotificationListenerService.REASON_CLICK;
 
 import static com.android.systemui.statusbar.phone.CentralSurfaces.getActivityOptions;
+import static com.android.systemui.util.kotlin.NullabilityKt.expectNotNull;
 
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -112,6 +113,8 @@
                 boolean showOverTheLockScreen);
     }
 
+    private final static String TAG = "StatusBarNotificationActivityStarter";
+
     private final Context mContext;
     private final int mDisplayId;
 
@@ -229,6 +232,7 @@
      */
     @Override
     public void onNotificationBubbleIconClicked(NotificationEntry entry) {
+        expectNotNull(TAG, "entry", entry);
         Runnable action = () -> {
             mBubblesManagerOptional.ifPresent(bubblesManager ->
                     bubblesManager.onUserChangedBubble(entry, !entry.isBubble()));
@@ -255,6 +259,8 @@
      */
     @Override
     public void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row) {
+        expectNotNull(TAG, "entry", entry);
+        expectNotNull(TAG, "row", row);
         mLogger.logStartingActivityFromClick(entry, row.isHeadsUpState(),
                 mKeyguardStateController.isVisible(),
                 mNotificationShadeWindowController.getPanelExpanded());
@@ -437,6 +443,7 @@
      */
     @Override
     public void onDragSuccess(NotificationEntry entry) {
+        expectNotNull(TAG, "entry", entry);
         // this method is not responsible for intent sending.
         // will focus follow operation only after drag-and-drop that notification.
         final NotificationVisibility nv = mVisibilityProvider.obtain(entry, true);
@@ -529,6 +536,8 @@
     @Override
     public void startNotificationGutsIntent(final Intent intent, final int appUid,
             ExpandableNotificationRow row) {
+        expectNotNull(TAG, "intent", intent);
+        expectNotNull(TAG, "row", row);
         boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
         ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Nullability.kt
similarity index 67%
rename from packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
rename to packages/SystemUI/src/com/android/systemui/util/kotlin/Nullability.kt
index 298dacd..1c760be 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Nullability.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.util.kotlin
 
+import android.util.Log
 import java.util.Optional
 
 /**
@@ -28,3 +29,14 @@
  */
 @Suppress("NOTHING_TO_INLINE")
 inline fun <T> Optional<T>.getOrNull(): T? = orElse(null)
+
+/**
+ * Utility method to check if a value that is technically nullable is actually null. If it is null,
+ * this will crash development builds (but just log on production/droidfood builds). It can be used
+ * as a first step to verify if a nullable value can be made non-nullable instead.
+ */
+fun <T> expectNotNull(logTag: String, name: String, nullable: T?) {
+    if (nullable == null) {
+        Log.wtf(logTag, "Expected value of $name to not be null.")
+    }
+}
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 d37d8f3..dbb3e38 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
@@ -19,14 +19,15 @@
 import android.content.res.mainResources
 import androidx.lifecycle.LifecycleCoroutineScope
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.qs.footerActionsController
 import com.android.systemui.qs.footerActionsViewModelFactory
+import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
 import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModel
 import com.android.systemui.shade.largeScreenHeaderHelper
 import com.android.systemui.shade.transition.largeScreenShadeInterpolator
 import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
-import com.android.systemui.statusbar.phone.keyguardBypassController
 import com.android.systemui.statusbar.sysuiStatusBarStateController
 
 val Kosmos.qsFragmentComposeViewModelFactory by
@@ -41,11 +42,12 @@
                     footerActionsViewModelFactory,
                     footerActionsController,
                     sysuiStatusBarStateController,
-                    keyguardBypassController,
+                    deviceEntryInteractor,
                     disableFlagsRepository,
                     largeScreenShadeInterpolator,
                     configurationInteractor,
                     largeScreenHeaderHelper,
+                    tileSquishinessInteractor,
                     lifecycleScope,
                 )
             }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepositoryKosmos.kt
new file mode 100644
index 0000000..d9fad32
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/TileSquishinessRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.tileSquishinessRepository by Kosmos.Fixture { TileSquishinessRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
index 3f62b4d..546129f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
@@ -20,6 +20,9 @@
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
 import com.android.systemui.qs.panels.ui.viewmodel.fixedColumnsSizeViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.tileSquishinessViewModel
 
 val Kosmos.infiniteGridLayout by
-    Kosmos.Fixture { InfiniteGridLayout(iconTilesViewModel, fixedColumnsSizeViewModel) }
+    Kosmos.Fixture {
+        InfiniteGridLayout(iconTilesViewModel, fixedColumnsSizeViewModel, tileSquishinessViewModel)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractorKosmos.kt
new file mode 100644
index 0000000..23db70f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/TileSquishinessInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.data.repository.tileSquishinessRepository
+
+val Kosmos.tileSquishinessInteractor by
+    Kosmos.Fixture { TileSquishinessInteractor(tileSquishinessRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
index 40d2624..babbd50 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
@@ -27,6 +27,7 @@
             currentTilesInteractor,
             fixedColumnsSizeViewModel,
             quickQuickSettingsRowInteractor,
+            tileSquishinessViewModel,
             iconTilesViewModel,
             applicationCoroutineScope,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModelKosmos.kt
new file mode 100644
index 0000000..ecc8cd1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileSquishinessViewModelKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
+
+val Kosmos.tileSquishinessViewModel by
+    Kosmos.Fixture { TileSquishinessViewModel(tileSquishinessInteractor) }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 082459b..b6c8fc7 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -16,22 +16,35 @@
 
 package com.android.server.appfunctions;
 
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DISABLED;
+import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_ENABLED;
+import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB;
+import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_NAMESPACE;
+
 import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.WorkerThread;
-import android.app.appfunctions.AppFunctionService;
+import android.app.appfunctions.AppFunctionManager;
+import android.app.appfunctions.AppFunctionManagerHelper;
+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;
 import android.app.appfunctions.ICancellationCallback;
 import android.app.appfunctions.IExecuteAppFunctionCallback;
 import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback;
+import android.app.appsearch.AppSearchBatchResult;
 import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.AppSearchManager.SearchContext;
 import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GetByDocumentIdRequest;
+import android.app.appsearch.PutDocumentsRequest;
 import android.app.appsearch.observer.DocumentChangeInfo;
 import android.app.appsearch.observer.ObserverCallback;
 import android.app.appsearch.observer.ObserverSpec;
@@ -40,12 +53,15 @@
 import android.content.Intent;
 import android.os.Binder;
 import android.os.CancellationSignal;
-import android.os.IBinder;
 import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
+import android.os.ParcelableException;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.infra.AndroidFuture;
 import com.android.server.SystemService.TargetUser;
@@ -54,6 +70,7 @@
 
 import java.util.Objects;
 import java.util.concurrent.CompletionException;
+import java.util.concurrent.Executor;
 
 /** Implementation of the AppFunctionManagerService. */
 public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
@@ -64,6 +81,7 @@
     private final ServiceHelper mInternalServiceHelper;
     private final ServiceConfig mServiceConfig;
     private final Context mContext;
+    private final Object mLock = new Object();
 
     public AppFunctionManagerServiceImpl(@NonNull Context context) {
         this(
@@ -180,52 +198,214 @@
             return;
         }
 
-        var unused =
-                mCallerValidator
-                        .verifyCallerCanExecuteAppFunction(
-                                callingUid,
-                                callingPid,
-                                requestInternal.getCallingPackage(),
-                                targetPackageName,
-                                requestInternal.getClientRequest().getFunctionIdentifier())
-                        .thenAccept(
-                                canExecute -> {
-                                    if (!canExecute) {
-                                        safeExecuteAppFunctionCallback.onResult(
-                                                ExecuteAppFunctionResponse.newFailure(
-                                                        ExecuteAppFunctionResponse.RESULT_DENIED,
-                                                        "Caller does not have permission to execute"
-                                                                + " the appfunction",
-                                                        /* extras= */ null));
-                                        return;
-                                    }
-                                    Intent serviceIntent =
-                                            mInternalServiceHelper.resolveAppFunctionService(
-                                                    targetPackageName, targetUser);
-                                    if (serviceIntent == null) {
-                                        safeExecuteAppFunctionCallback.onResult(
-                                                ExecuteAppFunctionResponse.newFailure(
-                                                        ExecuteAppFunctionResponse
-                                                                .RESULT_INTERNAL_ERROR,
-                                                        "Cannot find the target service.",
-                                                        /* extras= */ null));
-                                        return;
-                                    }
-                                    bindAppFunctionServiceUnchecked(
-                                            requestInternal,
-                                            serviceIntent,
-                                            targetUser,
-                                            localCancelTransport,
-                                            safeExecuteAppFunctionCallback,
-                                            /* bindFlags= */ Context.BIND_AUTO_CREATE
-                                                    | Context.BIND_FOREGROUND_SERVICE);
-                                })
-                        .exceptionally(
-                                ex -> {
-                                    safeExecuteAppFunctionCallback.onResult(
-                                            mapExceptionToExecuteAppFunctionResponse(ex));
-                                    return null;
-                                });
+        mCallerValidator
+                .verifyCallerCanExecuteAppFunction(
+                        callingUid,
+                        callingPid,
+                        requestInternal.getCallingPackage(),
+                        targetPackageName,
+                        requestInternal.getClientRequest().getFunctionIdentifier())
+                .thenAccept(
+                        canExecute -> {
+                            if (!canExecute) {
+                                safeExecuteAppFunctionCallback.onResult(
+                                        ExecuteAppFunctionResponse.newFailure(
+                                                ExecuteAppFunctionResponse.RESULT_DENIED,
+                                                "Caller does not have permission to execute the"
+                                                        + " appfunction",
+                                                /* extras= */ null));
+                            }
+                        })
+                .thenCompose(
+                        isEnabled ->
+                                isAppFunctionEnabled(
+                                        requestInternal.getClientRequest().getFunctionIdentifier(),
+                                        requestInternal.getClientRequest().getTargetPackageName(),
+                                        getAppSearchManagerAsUser(requestInternal.getUserHandle()),
+                                        THREAD_POOL_EXECUTOR))
+                .thenAccept(
+                        isEnabled -> {
+                            if (!isEnabled) {
+                                throw new DisabledAppFunctionException(
+                                        "The app function is disabled");
+                            }
+                        })
+                .thenAccept(
+                        unused -> {
+                            Intent serviceIntent =
+                                    mInternalServiceHelper.resolveAppFunctionService(
+                                            targetPackageName, targetUser);
+                            if (serviceIntent == null) {
+                                safeExecuteAppFunctionCallback.onResult(
+                                        ExecuteAppFunctionResponse.newFailure(
+                                                ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+                                                "Cannot find the target service.",
+                                                /* extras= */ null));
+                                return;
+                            }
+                            bindAppFunctionServiceUnchecked(
+                                    requestInternal,
+                                    serviceIntent,
+                                    targetUser,
+                                    localCancelTransport,
+                                    safeExecuteAppFunctionCallback,
+                                    /* bindFlags= */ Context.BIND_AUTO_CREATE
+                                            | Context.BIND_FOREGROUND_SERVICE);
+                        })
+                .exceptionally(
+                        ex -> {
+                            safeExecuteAppFunctionCallback.onResult(
+                                    mapExceptionToExecuteAppFunctionResponse(ex));
+                            return null;
+                        });
+    }
+
+    private static AndroidFuture<Boolean> isAppFunctionEnabled(
+            @NonNull String functionIdentifier,
+            @NonNull String targetPackage,
+            @NonNull AppSearchManager appSearchManager,
+            @NonNull Executor executor) {
+        AndroidFuture<Boolean> future = new AndroidFuture<>();
+        AppFunctionManagerHelper.isAppFunctionEnabled(
+                functionIdentifier,
+                targetPackage,
+                appSearchManager,
+                executor,
+                new OutcomeReceiver<>() {
+                    @Override
+                    public void onResult(@NonNull Boolean result) {
+                        future.complete(result);
+                    }
+
+                    @Override
+                    public void onError(@NonNull Exception error) {
+                        future.completeExceptionally(error);
+                    }
+                });
+        return future;
+    }
+
+    @Override
+    public void setAppFunctionEnabled(
+            @NonNull String callingPackage,
+            @NonNull String functionIdentifier,
+            @NonNull UserHandle userHandle,
+            @AppFunctionManager.EnabledState int enabledState,
+            @NonNull IAppFunctionEnabledCallback callback) {
+        try {
+            mCallerValidator.validateCallingPackage(callingPackage);
+        } catch (SecurityException e) {
+            reportException(callback, e);
+            return;
+        }
+        THREAD_POOL_EXECUTOR.execute(
+                () -> {
+                    try {
+                        // TODO(357551503): Instead of holding a global lock, hold a per-package
+                        //  lock.
+                        synchronized (mLock) {
+                            setAppFunctionEnabledInternalLocked(
+                                    callingPackage, functionIdentifier, userHandle, enabledState);
+                        }
+                        callback.onSuccess();
+                    } catch (Exception e) {
+                        Slog.e(TAG, "Error in setAppFunctionEnabled: ", e);
+                        reportException(callback, e);
+                    }
+                });
+    }
+
+    private static void reportException(
+            @NonNull IAppFunctionEnabledCallback callback, @NonNull Exception exception) {
+        try {
+            callback.onError(new ParcelableException(exception));
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to report the exception", e);
+        }
+    }
+
+    /**
+     * Sets the enabled status of a specified app function.
+     * <p>
+     * Required to hold a lock to call this function to avoid document changes during the process.
+     */
+    @WorkerThread
+    @GuardedBy("mLock")
+    private void setAppFunctionEnabledInternalLocked(
+            @NonNull String callingPackage,
+            @NonNull String functionIdentifier,
+            @NonNull UserHandle userHandle,
+            @AppFunctionManager.EnabledState int enabledState)
+            throws Exception {
+        AppSearchManager perUserAppSearchManager = getAppSearchManagerAsUser(userHandle);
+
+        if (perUserAppSearchManager == null) {
+            throw new IllegalStateException(
+                    "AppSearchManager not found for user:" + userHandle.getIdentifier());
+        }
+        SearchContext runtimeMetadataSearchContext =
+                new SearchContext.Builder(APP_FUNCTION_RUNTIME_METADATA_DB).build();
+
+        try (FutureAppSearchSession runtimeMetadataSearchSession =
+                new FutureAppSearchSessionImpl(
+                        perUserAppSearchManager,
+                        THREAD_POOL_EXECUTOR,
+                        runtimeMetadataSearchContext)) {
+            AppFunctionRuntimeMetadata existingMetadata =
+                    new AppFunctionRuntimeMetadata(
+                            getRuntimeMetadataGenericDocument(
+                                    callingPackage,
+                                    functionIdentifier,
+                                    runtimeMetadataSearchSession));
+            AppFunctionRuntimeMetadata.Builder newMetadata =
+                    new AppFunctionRuntimeMetadata.Builder(existingMetadata);
+            switch (enabledState) {
+                case AppFunctionManager.APP_FUNCTION_STATE_DEFAULT -> {
+                    newMetadata.setEnabled(null);
+                }
+                case APP_FUNCTION_STATE_ENABLED -> {
+                    newMetadata.setEnabled(true);
+                }
+                case APP_FUNCTION_STATE_DISABLED -> {
+                    newMetadata.setEnabled(false);
+                }
+                default ->
+                        throw new IllegalArgumentException(
+                                "Value of EnabledState is unsupported.");
+            }
+            AppSearchBatchResult<String, Void> putDocumentBatchResult =
+                    runtimeMetadataSearchSession
+                            .put(
+                                    new PutDocumentsRequest.Builder()
+                                            .addGenericDocuments(newMetadata.build())
+                                            .build())
+                            .get();
+            if (!putDocumentBatchResult.isSuccess()) {
+                throw new IllegalStateException("Failed writing updated doc to AppSearch due to "
+                        + putDocumentBatchResult);
+            }
+        }
+    }
+
+    @WorkerThread
+    @NonNull
+    private AppFunctionRuntimeMetadata getRuntimeMetadataGenericDocument(
+            @NonNull String packageName,
+            @NonNull String functionId,
+            @NonNull FutureAppSearchSession runtimeMetadataSearchSession)
+            throws Exception {
+        String documentId =
+                AppFunctionRuntimeMetadata.getDocumentIdForAppFunction(packageName, functionId);
+        GetByDocumentIdRequest request =
+                new GetByDocumentIdRequest.Builder(APP_FUNCTION_RUNTIME_NAMESPACE)
+                        .addIds(documentId)
+                        .build();
+        AppSearchBatchResult<String, GenericDocument> result =
+                runtimeMetadataSearchSession.getByDocumentId(request).get();
+        if (result.isSuccess()) {
+            return new AppFunctionRuntimeMetadata((result.getSuccesses().get(documentId)));
+        }
+        throw new IllegalArgumentException("Function " + functionId + " does not exist");
     }
 
     private void bindAppFunctionServiceUnchecked(
@@ -302,24 +482,27 @@
         }
     }
 
+    private AppSearchManager getAppSearchManagerAsUser(@NonNull UserHandle userHandle) {
+        return mContext.createContextAsUser(userHandle, /* flags= */ 0)
+                .getSystemService(AppSearchManager.class);
+    }
+
     private ExecuteAppFunctionResponse mapExceptionToExecuteAppFunctionResponse(Throwable e) {
         if (e instanceof CompletionException) {
             e = e.getCause();
         }
-
-        if (e instanceof AppSearchException) {
-            AppSearchException appSearchException = (AppSearchException) e;
-            return ExecuteAppFunctionResponse.newFailure(
+        int resultCode = ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR;
+        if (e instanceof AppSearchException appSearchException) {
+            resultCode =
                     mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(
-                            appSearchException.getResultCode()),
-                    appSearchException.getMessage(),
-                    /* extras= */ null);
+                            appSearchException.getResultCode());
+        } else if (e instanceof SecurityException) {
+            resultCode = ExecuteAppFunctionResponse.RESULT_DENIED;
+        } else if (e instanceof DisabledAppFunctionException) {
+            resultCode = ExecuteAppFunctionResponse.RESULT_DISABLED;
         }
-
         return ExecuteAppFunctionResponse.newFailure(
-                ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
-                e.getMessage(),
-                /* extras= */ null);
+                resultCode, e.getMessage(), /* extras= */ null);
     }
 
     private int mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(int resultCode) {
@@ -434,4 +617,11 @@
             }
         }
     }
+
+    /** Throws when executing a disabled app function. */
+    private static class DisabledAppFunctionException extends RuntimeException {
+        private DisabledAppFunctionException(@NonNull String errorMessage) {
+            super(errorMessage);
+        }
+    }
 }
diff --git a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java
index 070a99d..ffca849 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java
@@ -65,8 +65,7 @@
             @NonNull UserHandle userHandle,
             @NonNull RunServiceCallCallback<T> callback) {
         OneOffServiceConnection serviceConnection =
-                new OneOffServiceConnection(
-                        intent, bindFlags, userHandle, callback);
+                new OneOffServiceConnection(intent, bindFlags, userHandle, callback);
 
         return serviceConnection.bindAndRun();
     }
@@ -93,7 +92,7 @@
             boolean bindServiceResult =
                     mContext.bindServiceAsUser(mIntent, this, mFlags, mUserHandle);
 
-            if(!bindServiceResult) {
+            if (!bindServiceResult) {
                 safeUnbind();
             }
 
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 5b271a3..7474df2 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -87,7 +87,7 @@
 # replaces 27510 with a row per notification
 27531 notification_visibility (key|3),(visibile|1),(lifespan|1),(freshness|1),(exposure|1),(rank|1)
 # a notification emited noise, vibration, or light
-27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1),(politeness|1)
+27532 notification_alert (key|3),(buzz|1),(beep|1),(blink|1),(politeness|1),(mute_reason|1)
 # a notification was added to a autogroup
 27533 notification_autogrouped (key|3)
 # notification was removed from an autogroup
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 62d8761..03fec011 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1480,29 +1480,24 @@
             brightnessState = clampScreenBrightness(brightnessState);
         }
 
-        if (useDozeBrightness) {
-            // TODO(b/329676661): Introduce a config property to choose between this brightness
-            //  strategy and DOZE_DEFAULT
-            // On some devices, when auto-brightness is disabled and the device is dozing, we use
-            // the current brightness setting scaled by the doze scale factor
-            if ((Float.isNaN(brightnessState)
-                    || displayBrightnessState.getDisplayBrightnessStrategyName()
-                    .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME))
-                    && mFlags.isDisplayOffloadEnabled()
-                    && mDisplayOffloadSession != null
+        if (useDozeBrightness && (Float.isNaN(brightnessState)
+                || displayBrightnessState.getDisplayBrightnessStrategyName()
+                .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME))) {
+            if (mFlags.isDisplayOffloadEnabled() && mDisplayOffloadSession != null
                     && (mAutomaticBrightnessController == null
                     || !mAutomaticBrightnessStrategy.shouldUseAutoBrightness())) {
+                // TODO(b/329676661): Introduce a config property to choose between this brightness
+                //  strategy and DOZE_DEFAULT
+                // On some devices, when auto-brightness is disabled and the device is dozing, we
+                // use the current brightness setting scaled by the doze scale factor
                 rawBrightnessState = getDozeBrightnessForOffload();
                 brightnessState = clampScreenBrightness(rawBrightnessState);
                 updateScreenBrightnessSetting = false;
                 mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_MANUAL);
                 mTempBrightnessEvent.setFlags(
                         mTempBrightnessEvent.getFlags() | BrightnessEvent.FLAG_DOZE_SCALE);
-            }
-
-            // Use default brightness when dozing unless overridden.
-            if (Float.isNaN(brightnessState)
-                    && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()) {
+            } else if (!mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()) {
+                // Use default brightness when dozing unless overridden.
                 rawBrightnessState = mScreenBrightnessDozeConfig;
                 brightnessState = clampScreenBrightness(rawBrightnessState);
                 mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT);
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index abb2132..06f419a 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -118,6 +118,37 @@
             Intent.ACTION_MANAGED_PROFILE_AVAILABLE, new Pair<>(Intent.EXTRA_QUIET_MODE, false)
     );
 
+    // Bits 1, 2, 3, 4 are already taken by: beep|buzz|blink|cooldown
+    static final int MUTE_REASON_NOT_MUTED = 0;
+    static final int MUTE_REASON_NOT_AUDIBLE = 1 << 5;
+    static final int MUTE_REASON_SILENT_UPDATE = 1 << 6;
+    static final int MUTE_REASON_POST_SILENTLY = 1 << 7;
+    static final int MUTE_REASON_LISTENER_HINT = 1 << 8;
+    static final int MUTE_REASON_DND = 1 << 9;
+    static final int MUTE_REASON_GROUP_ALERT = 1 << 10;
+    static final int MUTE_REASON_FLAG_SILENT = 1 << 11;
+    static final int MUTE_REASON_RATE_LIMIT = 1 << 12;
+    static final int MUTE_REASON_OTHER_INSISTENT_PLAYING = 1 << 13;
+    static final int MUTE_REASON_SUPPRESSED_BUBBLE = 1 << 14;
+    static final int MUTE_REASON_COOLDOWN = 1 << 15;
+
+    @IntDef(prefix = { "MUTE_REASON_" }, value = {
+        MUTE_REASON_NOT_MUTED,
+        MUTE_REASON_NOT_AUDIBLE,
+        MUTE_REASON_SILENT_UPDATE,
+        MUTE_REASON_POST_SILENTLY,
+        MUTE_REASON_LISTENER_HINT,
+        MUTE_REASON_DND,
+        MUTE_REASON_GROUP_ALERT,
+        MUTE_REASON_FLAG_SILENT,
+        MUTE_REASON_RATE_LIMIT,
+        MUTE_REASON_OTHER_INSISTENT_PLAYING,
+        MUTE_REASON_SUPPRESSED_BUBBLE,
+        MUTE_REASON_COOLDOWN,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface MuteReason {}
+
     private final Context mContext;
     private final PackageManager mPackageManager;
     private final TelephonyManager mTelephonyManager;
@@ -388,6 +419,7 @@
         boolean buzz = false;
         boolean beep = false;
         boolean blink = false;
+        @MuteReason int shouldMuteReason = MUTE_REASON_NOT_MUTED;
 
         final String key = record.getKey();
 
@@ -395,10 +427,6 @@
             Log.d(TAG, "buzzBeepBlinkLocked " + record);
         }
 
-        if (isPoliteNotificationFeatureEnabled(record)) {
-            mStrategy.onNotificationPosted(record);
-        }
-
         // Should this notification make noise, vibe, or use the LED?
         final boolean aboveThreshold =
                 mIsAutomotive
@@ -443,7 +471,8 @@
                 boolean vibrateOnly =
                         hasValidVibrate && mNotificationCooldownVibrateUnlocked && mUserPresent;
                 boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
-                if (hasAudibleAlert && !shouldMuteNotificationLocked(record, signals)) {
+                shouldMuteReason = shouldMuteNotificationLocked(record, signals, hasAudibleAlert);
+                if (shouldMuteReason == MUTE_REASON_NOT_MUTED) {
                     if (!sentAccessibilityEvent) {
                         sendAccessibilityEvent(record);
                         sentAccessibilityEvent = true;
@@ -541,15 +570,17 @@
             }
         }
         final int buzzBeepBlinkLoggingCode =
-                (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0) | getPoliteBit(record);
+                (buzz ? 1 : 0) | (beep ? 2 : 0) | (blink ? 4 : 0)
+                | getPoliteBit(record) | shouldMuteReason;
         if (buzzBeepBlinkLoggingCode > 0) {
             MetricsLogger.action(record.getLogMaker()
                     .setCategory(MetricsEvent.NOTIFICATION_ALERT)
                     .setType(MetricsEvent.TYPE_OPEN)
                     .setSubtype(buzzBeepBlinkLoggingCode));
             EventLogTags.writeNotificationAlert(key, buzz ? 1 : 0, beep ? 1 : 0, blink ? 1 : 0,
-                    getPolitenessState(record));
+                    getPolitenessState(record), shouldMuteReason);
         }
+
         if (Flags.politeNotifications()) {
             // Update last alert time
             if (buzz || beep) {
@@ -594,41 +625,46 @@
                 mNMP.getNotificationByKey(mVibrateNotificationKey));
     }
 
-    boolean shouldMuteNotificationLocked(final NotificationRecord record, final Signals signals) {
+    @MuteReason int shouldMuteNotificationLocked(final NotificationRecord record,
+            final Signals signals, boolean hasAudibleAlert) {
+        // Suppressed because no audible alert
+        if (!hasAudibleAlert) {
+            return MUTE_REASON_NOT_AUDIBLE;
+        }
         // Suppressed because it's a silent update
         final Notification notification = record.getNotification();
         if (record.isUpdate && (notification.flags & FLAG_ONLY_ALERT_ONCE) != 0) {
-            return true;
+            return MUTE_REASON_SILENT_UPDATE;
         }
 
         // Suppressed because a user manually unsnoozed something (or similar)
         if (record.shouldPostSilently()) {
-            return true;
+            return MUTE_REASON_POST_SILENTLY;
         }
 
         // muted by listener
         final String disableEffects = disableNotificationEffects(record, signals.listenerHints);
         if (disableEffects != null) {
             ZenLog.traceDisableEffects(record, disableEffects);
-            return true;
+            return MUTE_REASON_LISTENER_HINT;
         }
 
         // suppressed due to DND
         if (record.isIntercepted()) {
-            return true;
+            return MUTE_REASON_DND;
         }
 
         // Suppressed because another notification in its group handles alerting
         if (record.getSbn().isGroup()) {
             if (notification.suppressAlertingDueToGrouping()) {
-                return true;
+                return MUTE_REASON_GROUP_ALERT;
             }
         }
 
         // Suppressed because notification was explicitly flagged as silent
         if (android.service.notification.Flags.notificationSilentFlag()) {
             if (notification.isSilent()) {
-                return true;
+                return MUTE_REASON_FLAG_SILENT;
             }
         }
 
@@ -636,12 +672,12 @@
         final String pkg = record.getSbn().getPackageName();
         if (mUsageStats.isAlertRateLimited(pkg)) {
             Slog.e(TAG, "Muting recently noisy " + record.getKey());
-            return true;
+            return MUTE_REASON_RATE_LIMIT;
         }
 
         // A different looping ringtone, such as an incoming call is playing
         if (isCurrentlyInsistent() && !isInsistentUpdate(record)) {
-            return true;
+            return MUTE_REASON_OTHER_INSISTENT_PLAYING;
         }
 
         // Suppressed since it's a non-interruptive update to a bubble-suppressed notification
@@ -650,11 +686,23 @@
         if (record.isUpdate && !record.isInterruptive() && isBubbleOrOverflowed
                 && record.getNotification().getBubbleMetadata() != null) {
             if (record.getNotification().getBubbleMetadata().isNotificationSuppressed()) {
-                return true;
+                return MUTE_REASON_SUPPRESSED_BUBBLE;
             }
         }
 
-        return false;
+        if (isPoliteNotificationFeatureEnabled(record)) {
+            // Notify the politeness strategy that an alerting notification is posted
+            if (!isInsistentUpdate(record)) {
+                mStrategy.onNotificationPosted(record);
+            }
+
+            // Suppress if politeness is muted and it's not an update for insistent
+            if (getPolitenessState(record) == PolitenessStrategy.POLITE_STATE_MUTED) {
+                return MUTE_REASON_COOLDOWN;
+            }
+        }
+
+        return MUTE_REASON_NOT_MUTED;
     }
 
     private boolean isLoopingRingtoneNotification(final NotificationRecord playingRecord) {
@@ -1201,12 +1249,6 @@
             mApplyPerPackage = applyPerPackage;
         }
 
-        boolean shouldIgnoreNotification(final NotificationRecord record) {
-            // Ignore auto-group summaries => don't count them as app-posted notifications
-            // for the cooldown budget
-            return (record.getSbn().isGroup() && GroupHelper.isAggregatedGroup(record));
-        }
-
         /**
          * Get the key that determines the grouping for the cooldown behavior.
          *
@@ -1358,10 +1400,6 @@
 
         @Override
         public void onNotificationPosted(final NotificationRecord record) {
-            if (shouldIgnoreNotification(record)) {
-                return;
-            }
-
             long timeSinceLastNotif =
                     System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
 
@@ -1434,10 +1472,6 @@
         @Override
         void onNotificationPosted(NotificationRecord record) {
             if (isAvalancheActive()) {
-                if (shouldIgnoreNotification(record)) {
-                    return;
-                }
-
                 long timeSinceLastNotif =
                     System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index f8e8ca4..14e9180 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3174,14 +3174,23 @@
         return getWindowConfiguration().canReceiveKeys() && !mWaitForEnteringPinnedMode;
     }
 
-    boolean isResizeable() {
-        return isResizeable(/* checkPictureInPictureSupport */ true);
+    /**
+     * Returns {@code true} if the fixed orientation, aspect ratio, resizability of this activity
+     * will be ignored.
+     */
+    boolean isUniversalResizeable() {
+        return mWmService.mConstants.mIgnoreActivityOrientationRequest
+                && info.applicationInfo.category != ApplicationInfo.CATEGORY_GAME
+                // If the user preference respects aspect ratio, then it becomes non-resizable.
+                && !mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
+                        .shouldApplyUserMinAspectRatioOverride();
     }
 
-    boolean isResizeable(boolean checkPictureInPictureSupport) {
+    boolean isResizeable() {
         return mAtmService.mForceResizableActivities
                 || ActivityInfo.isResizeableMode(info.resizeMode)
-                || (info.supportsPictureInPicture() && checkPictureInPictureSupport)
+                || info.supportsPictureInPicture()
+                || isUniversalResizeable()
                 // If the activity can be embedded, it should inherit the bounds of task fragment.
                 || isEmbedded();
     }
@@ -8165,11 +8174,8 @@
     @Override
     @ActivityInfo.ScreenOrientation
     protected int getOverrideOrientation() {
-        final int candidateOrientation;
-        if (!mWmService.mConstants.mIgnoreActivityOrientationRequest
-                || info.applicationInfo.category == ApplicationInfo.CATEGORY_GAME) {
-            candidateOrientation = super.getOverrideOrientation();
-        } else {
+        int candidateOrientation = super.getOverrideOrientation();
+        if (isUniversalResizeable() && ActivityInfo.isFixedOrientation(candidateOrientation)) {
             candidateOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
         }
         return mAppCompatController.getOrientationPolicy()
@@ -10028,7 +10034,7 @@
         }
         StringBuilder sb = new StringBuilder(128);
         sb.append("ActivityRecord{");
-        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(System.identityHashCode(this));
         sb.append(" u");
         sb.append(mUserId);
         sb.append(' ');
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 4092a0b..2ba300a 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1029,6 +1029,7 @@
                 if (requestCode >= 0 && !sourceRecord.finishing) {
                     resultRecord = sourceRecord;
                 }
+                request.logMessage.append(" (sr=" + System.identityHashCode(sourceRecord) + ")");
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 51ef87d..6946b6a 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -114,9 +114,6 @@
             return mTransparentPolicy.getInheritedMinAspectRatio();
         }
         final ActivityInfo info = mActivityRecord.info;
-        if (info.applicationInfo == null) {
-            return info.getMinAspectRatio();
-        }
         final AppCompatAspectRatioOverrides aspectRatioOverrides =
                 mAppCompatOverrides.getAppCompatAspectRatioOverrides();
         if (aspectRatioOverrides.shouldApplyUserMinAspectRatioOverride()) {
@@ -128,6 +125,9 @@
                         mActivityRecord);
         if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
                 && !shouldOverrideMinAspectRatioForCamera) {
+            if (mActivityRecord.isUniversalResizeable()) {
+                return 0;
+            }
             return info.getMinAspectRatio();
         }
 
@@ -170,6 +170,9 @@
         if (mTransparentPolicy.isRunning()) {
             return mTransparentPolicy.getInheritedMaxAspectRatio();
         }
+        if (mActivityRecord.isUniversalResizeable()) {
+            return 0;
+        }
         return mActivityRecord.info.getMaxAspectRatio();
     }
 
diff --git a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
index 1924691..6e6f76a 100644
--- a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
@@ -188,10 +188,6 @@
         }
 
         final ActivityInfo info = mActivityRecord.info;
-        if (info.applicationInfo == null) {
-            return info.getMinAspectRatio();
-        }
-
         final AppCompatAspectRatioOverrides aspectRatioOverrides =
                 mAppCompatOverrides.getAppCompatAspectRatioOverrides();
         if (shouldApplyUserMinAspectRatioOverride(task)) {
@@ -203,6 +199,9 @@
                 && dc.mAppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord);
         if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
                 && !shouldOverrideMinAspectRatioForCamera) {
+            if (mActivityRecord.isUniversalResizeable()) {
+                return 0;
+            }
             return info.getMinAspectRatio();
         }
 
@@ -246,6 +245,9 @@
         if (mTransparentPolicy.isRunning()) {
             return mTransparentPolicy.getInheritedMaxAspectRatio();
         }
+        if (mActivityRecord.isUniversalResizeable()) {
+            return 0;
+        }
         return mActivityRecord.info.getMaxAspectRatio();
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index e6f6215..1ac0bb0 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2156,6 +2156,11 @@
         }
         mDecorInsets.invalidate();
         mDecorInsets.mInfoForRotation[rotation].set(newInfo);
+        if (!mService.mDisplayEnabled) {
+            // There could be other pending changes during booting. It might be better to let the
+            // clients receive the new states earlier.
+            return true;
+        }
         return !sameConfigFrame;
     }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 14f034b..edbc328 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1475,7 +1475,7 @@
             // The starting window should keep covering its task when a pure TaskFragment is added
             // because its bounds may not fill the task.
             final ActivityRecord top = getTopMostActivity();
-            if (top != null) {
+            if (top != null && !top.hasFixedRotationTransform()) {
                 top.associateStartingWindowWithTaskIfNeeded();
             }
         }
@@ -4707,8 +4707,13 @@
                         // If the moveToFront is a part of finishing transition, then make sure
                         // the z-order of tasks are up-to-date.
                         if (topActivity.mTransitionController.inFinishingTransition(topActivity)) {
-                            Transition.assignLayers(taskDisplayArea,
-                                    taskDisplayArea.getPendingTransaction());
+                            final SurfaceControl.Transaction tx =
+                                    taskDisplayArea.getPendingTransaction();
+                            Transition.assignLayers(taskDisplayArea, tx);
+                            final SurfaceControl leash = topActivity.getFixedRotationLeash();
+                            if (leash != null) {
+                                tx.setLayer(leash, topActivity.getLastLayer());
+                            }
                         }
                     }
                 }
diff --git a/services/profcollect/src/com/android/server/profcollect/Utils.java b/services/profcollect/src/com/android/server/profcollect/Utils.java
index 8508802..b4e2544 100644
--- a/services/profcollect/src/com/android/server/profcollect/Utils.java
+++ b/services/profcollect/src/com/android/server/profcollect/Utils.java
@@ -19,6 +19,7 @@
 import static com.android.server.profcollect.ProfcollectForwardingService.LOG_TAG;
 
 import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.provider.DeviceConfig;
 import android.util.Log;
 
@@ -42,7 +43,7 @@
         BackgroundThread.get().getThreadHandler().post(() -> {
             try {
                 mIProfcollect.trace_system(eventName);
-            } catch (RemoteException e) {
+            } catch (RemoteException | ServiceSpecificException e) {
                 Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
             }
         });
@@ -56,7 +57,7 @@
         BackgroundThread.get().getThreadHandler().postDelayed(() -> {
             try {
                 mIProfcollect.trace_system(eventName);
-            } catch (RemoteException e) {
+            } catch (RemoteException | ServiceSpecificException e) {
                 Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
             }
         }, delayMs);
@@ -73,10 +74,10 @@
                 mIProfcollect.trace_process(eventName,
                         processName,
                         durationMs);
-            } catch (RemoteException e) {
+            } catch (RemoteException | ServiceSpecificException e) {
                 Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
             }
         });
         return true;
     }
-}
\ No newline at end of file
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 62e5b9a..45cd571 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -31,6 +31,12 @@
 import static android.media.AudioAttributes.USAGE_NOTIFICATION;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
 
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_COOLDOWN;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_FLAG_SILENT;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_GROUP_ALERT;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_NOT_MUTED;
+import static com.android.server.notification.NotificationAttentionHelper.MUTE_REASON_OTHER_INSISTENT_PLAYING;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -106,6 +112,7 @@
 import com.android.internal.config.sysui.TestableFlagResolver;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.logging.InstanceIdSequenceFake;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.IntPair;
 import com.android.server.UiServiceTestCase;
 import com.android.server.lights.LightsManager;
@@ -1276,7 +1283,8 @@
         verifyNeverBeep();
         assertFalse(r.isInterruptive());
         assertEquals(-1, r.getLastAudiblyAlertedMs());
-        assertTrue(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS));
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS,
+                true)).isEqualTo(MUTE_REASON_FLAG_SILENT);
     }
 
     @Test
@@ -1295,7 +1303,8 @@
         verifyNeverBeep();
         assertFalse(r.isInterruptive());
         assertEquals(-1, r.getLastAudiblyAlertedMs());
-        assertTrue(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS));
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS,
+                true)).isEqualTo(MUTE_REASON_GROUP_ALERT);
     }
 
     @Test
@@ -1861,7 +1870,9 @@
         verifyBeepLooped();
 
         NotificationRecord interrupter = getBeepyOtherNotification();
-        assertTrue(mAttentionHelper.shouldMuteNotificationLocked(interrupter, DEFAULT_SIGNALS));
+        assertThat(
+                mAttentionHelper.shouldMuteNotificationLocked(interrupter, DEFAULT_SIGNALS,
+                true)).isEqualTo(MUTE_REASON_OTHER_INSISTENT_PLAYING);
         mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS);
 
         verifyBeep(1);
@@ -1879,16 +1890,16 @@
         ringtoneChannel.enableVibration(true);
         NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
         mService.addNotification(ringtoneNotification);
-        assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
-            DEFAULT_SIGNALS));
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+                DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
         mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
         verifyBeepLooped();
         verifyDelayedVibrateLooped();
         Mockito.reset(mVibrator);
         Mockito.reset(mRingtonePlayer);
 
-        assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
-            DEFAULT_SIGNALS));
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+                DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
         mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
 
         // beep wasn't reset
@@ -1907,8 +1918,8 @@
         ringtoneChannel.enableVibration(true);
         NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
         mService.addNotification(ringtoneNotification);
-        assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
-            DEFAULT_SIGNALS));
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+                DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
         mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
         verifyBeepLooped();
         verifyDelayedVibrateLooped();
@@ -1930,8 +1941,8 @@
         ringtoneChannel.enableVibration(true);
         NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
         mService.addNotification(ringtoneNotification);
-        assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
-            DEFAULT_SIGNALS));
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+                DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
         mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
         verifyBeepLooped();
         verifyNeverVibrate();
@@ -1951,14 +1962,15 @@
             new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
         ringtoneChannel.enableVibration(true);
         NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
-        assertFalse(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
-            DEFAULT_SIGNALS));
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(ringtoneNotification,
+                DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_NOT_MUTED);
 
         mAttentionHelper.buzzBeepBlinkLocked(ringtoneNotification, DEFAULT_SIGNALS);
         verifyVibrateLooped();
 
         NotificationRecord interrupter = getBuzzyOtherNotification();
-        assertTrue(mAttentionHelper.shouldMuteNotificationLocked(interrupter, DEFAULT_SIGNALS));
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(interrupter,
+                DEFAULT_SIGNALS, true)).isEqualTo(MUTE_REASON_OTHER_INSISTENT_PLAYING);
         mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS);
 
         verifyVibrate(1);
@@ -2260,10 +2272,13 @@
 
         // 2nd update should beep at 0% volume
         Mockito.reset(mRingtonePlayer);
-        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
-        verifyBeepVolume(0.0f);
+        int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
+        assertThat(mAttentionHelper.shouldMuteNotificationLocked(r, DEFAULT_SIGNALS, true))
+                .isEqualTo(MUTE_REASON_COOLDOWN);
 
-        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+        verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
         assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
@@ -2305,8 +2320,9 @@
 
         // 2nd update should beep at 0% volume
         Mockito.reset(mRingtonePlayer);
-        mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
-        verifyBeepVolume(0.0f);
+        int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
 
         verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
         assertEquals(-1, r3.getLastAudiblyAlertedMs());
@@ -2381,9 +2397,10 @@
                 false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
 
         // update should beep at 0% volume
-        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+        int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
         assertEquals(-1, r2.getLastAudiblyAlertedMs());
-        verifyBeepVolume(0.0f);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
 
         // Use different package for next notifications
         NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
@@ -2392,8 +2409,9 @@
 
         // 2nd update should beep at 0% volume
         Mockito.reset(mRingtonePlayer);
-        mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
-        verifyBeepVolume(0.0f);
+        buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
 
         verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
         assertEquals(-1, r3.getLastAudiblyAlertedMs());
@@ -2493,8 +2511,9 @@
 
         // Regular notification: should beep at 0% volume
         NotificationRecord r = getBeepyNotification();
-        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
-        verifyBeepVolume(0.0f);
+        int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
         assertEquals(-1, r.getLastAudiblyAlertedMs());
         Mockito.reset(mRingtonePlayer);
 
@@ -2525,8 +2544,9 @@
 
         // 2nd update should beep at 0% volume
         Mockito.reset(mRingtonePlayer);
-        mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
-        verifyBeepVolume(0.0f);
+        buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
 
         // Set important conversation
         mChannel.setImportantConversation(true);
@@ -2751,9 +2771,10 @@
         Mockito.reset(mRingtonePlayer);
 
         // next update at 0% volume
-        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
         assertEquals(-1, summary.getLastAudiblyAlertedMs());
-        verifyBeepVolume(0.0f);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
 
         verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
     }
@@ -2823,9 +2844,10 @@
 
         // 2nd update should beep at 0% volume
         Mockito.reset(mRingtonePlayer);
-        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+        int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
         assertEquals(-1, r2.getLastAudiblyAlertedMs());
-        verifyBeepVolume(0.0f);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
 
         // Use different package for next notifications
         NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
@@ -2891,6 +2913,94 @@
     }
 
     @Test
+    public void testBeepVolume_politeNotif_groupAlertSummary() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        // NOTIFICATION_COOLDOWN_ALL setting is enabled
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+        initAttentionHelper(flagResolver);
+
+        // child should beep at 0% volume
+        NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
+        mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertFalse(child.isInterruptive());
+        assertEquals(-1, child.getLastAudiblyAlertedMs());
+        Mockito.reset(mRingtonePlayer);
+
+        // child should beep at 0% volume
+        child = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
+        mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertFalse(child.isInterruptive());
+        assertEquals(-1, child.getLastAudiblyAlertedMs());
+        Mockito.reset(mRingtonePlayer);
+
+        // summary 100% volume (GROUP_ALERT_SUMMARY)
+        NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_SUMMARY);
+        summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+        verifyBeepVolume(1.0f);
+        Mockito.reset(mRingtonePlayer);
+
+        // next update at 50% volume because only summary was tracked as alerting
+        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
+        verifyBeepVolume(0.5f);
+
+        verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
+    }
+
+    @Test
+    public void testBeepVolume_politeNotif_groupAlertChildren() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        // NOTIFICATION_COOLDOWN_ALL setting is enabled
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+        initAttentionHelper(flagResolver);
+
+        // summary 0% volume (GROUP_ALERT_CHILDREN)
+        NotificationRecord summary = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
+        summary.getNotification().flags |= Notification.FLAG_GROUP_SUMMARY;
+        mAttentionHelper.buzzBeepBlinkLocked(summary, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertFalse(summary.isInterruptive());
+        assertEquals(-1, summary.getLastAudiblyAlertedMs());
+        Mockito.reset(mRingtonePlayer);
+
+        // child should beep at 100% volume
+        NotificationRecord child = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
+        mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+        assertNotEquals(-1, child.getLastAudiblyAlertedMs());
+        verifyBeepVolume(1.0f);
+        Mockito.reset(mRingtonePlayer);
+
+        // child should beep at 50% volume
+        child = getBeepyNotificationRecord("a", GROUP_ALERT_CHILDREN);
+        mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+        assertNotEquals(-1, child.getLastAudiblyAlertedMs());
+        verifyBeepVolume(0.5f);
+        Mockito.reset(mRingtonePlayer);
+
+        // child should beep at 0% volume
+        mAttentionHelper.buzzBeepBlinkLocked(child, DEFAULT_SIGNALS);
+        verifyNeverBeep();
+        assertTrue(child.isInterruptive());
+        assertEquals(-1, child.getLastAudiblyAlertedMs());
+
+        verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
+    }
+
+    @Test
     public void testVibrationIntensity_politeNotif() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
         TestableFlagResolver flagResolver = new TestableFlagResolver();
@@ -2914,8 +3024,9 @@
         Mockito.reset(vibratorHelper);
 
         // 2nd update should buzz at 0% intensity
-        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
-        verify(vibratorHelper, times(1)).scale(any(), eq(0.0f));
+        int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyNeverVibrate();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
     }
 
     @Test
@@ -3007,10 +3118,11 @@
 
         // 2nd update should beep at 0% volume
         Mockito.reset(mRingtonePlayer);
-        mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
-        verifyBeepVolume(0.0f);
+        int buzzBeepBlink = mAttentionHelper.buzzBeepBlinkLocked(r, WORK_PROFILE_SIGNALS);
+        verifyNeverBeep();
+        assertThat(buzzBeepBlink).isEqualTo(MetricsEvent.ALERT_MUTED | MUTE_REASON_COOLDOWN);
 
-        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+        verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
         assertEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
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 8fa4667..adc969c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4849,6 +4849,39 @@
     }
 
     @Test
+    public void testUniversalResizeable() {
+        mWm.mConstants.mIgnoreActivityOrientationRequest = true;
+        setUpApp(mDisplayContent);
+        final float maxAspect = 1.8f;
+        final float minAspect = 1.5f;
+        prepareLimitedBounds(mActivity, maxAspect, minAspect,
+                ActivityInfo.SCREEN_ORIENTATION_LOCKED, true /* isUnresizable */);
+
+        assertTrue(mActivity.isUniversalResizeable());
+        assertTrue(mActivity.isResizeable());
+        assertFalse(mActivity.shouldCreateAppCompatDisplayInsets());
+        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
+        assertEquals(mActivity.getTask().getBounds(), mActivity.getBounds());
+        final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivity.mAppCompatController
+                .getAppCompatAspectRatioPolicy();
+        assertEquals(0, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */);
+        assertEquals(0, aspectRatioPolicy.getMinAspectRatio(), 0 /* delta */);
+
+        // Compat override can still take effect.
+        final AppCompatAspectRatioOverrides aspectRatioOverrides =
+                mActivity.mAppCompatController.getAppCompatAspectRatioOverrides();
+        spyOn(aspectRatioOverrides);
+        doReturn(true).when(aspectRatioOverrides).shouldOverrideMinAspectRatio();
+        assertEquals(minAspect, aspectRatioPolicy.getMinAspectRatio(), 0 /* delta */);
+
+        // User override can still take effect.
+        doReturn(true).when(aspectRatioOverrides).shouldApplyUserMinAspectRatioOverride();
+        assertFalse(mActivity.isResizeable());
+        assertEquals(maxAspect, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */);
+        assertNotEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
+    }
+
+    @Test
     public void testClearSizeCompat_resetOverrideConfig() {
         final int origDensity = 480;
         final int newDensity = 520;
diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
index 82de070..8b65efd 100644
--- a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
index 4ffb11a..3382c1e 100644
--- a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
index 0fa4d07..e941e79 100644
--- a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
index 4d9fefb..4e06dca 100644
--- a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml
index b879c54..0cadd68 100644
--- a/tests/FlickerTests/IME/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- enable AOD -->
         <option name="set-secure-setting" key="doze_always_on" value="1" />
         <!-- prevents the phone from restarting -->
diff --git a/tests/FlickerTests/Notification/AndroidTestTemplate.xml b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
index 04b312a..f32e8bed 100644
--- a/tests/FlickerTests/Notification/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Notification/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
index 8acdabc..68ae4f1 100644
--- a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->
diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
index 91ece21..ec186723 100644
--- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
+++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml
@@ -12,6 +12,10 @@
         <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
         <!-- keeps the screen on during tests -->
         <option name="screen-always-on" value="on"/>
+        <!-- Turns off Wi-fi -->
+        <option name="wifi" value="off"/>
+        <!-- Turns off Bluetooth -->
+        <option name="bluetooth" value="off"/>
         <!-- prevents the phone from restarting -->
         <option name="force-skip-system-props" value="true"/>
         <!-- set WM tracing verbose level to all -->