Merge "[automerger skipped] Merge "Rescind BAL privilege when ShortcutService sends the callback PI" into udc-dev am: 4c452c359c am: c176df687a -s ours" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 5cfdeb9..6b5554b 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -18,8 +18,11 @@
 
     // Add java_aconfig_libraries to here to add them to the core framework
     srcs: [
+        ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
         ":android.os.flags-aconfig-java{.generated_srcjars}",
+        ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
         ":android.security.flags-aconfig-java{.generated_srcjars}",
+        ":android.media.flags-aconfig-java{.generated_srcjars}",
         ":camera_platform_flags_core_java_lib{.generated_srcjars}",
         ":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
         ":com.android.text.flags-aconfig-java{.generated_srcjars}",
@@ -79,6 +82,11 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+cc_aconfig_library {
+    name: "aconfig_text_flags_c_lib",
+    aconfig_declarations: "com.android.text.flags-aconfig",
+}
+
 // Security
 aconfig_declarations {
     name: "android.security.flags-aconfig",
@@ -100,6 +108,19 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// UsageStats
+aconfig_declarations {
+    name: "android.app.usage.flags-aconfig",
+    package: "android.app.usage",
+    srcs: ["core/java/android/app/usage/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.app.usage.flags-aconfig-java",
+    aconfig_declarations: "android.app.usage.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // OS
 aconfig_declarations {
     name: "android.os.flags-aconfig",
@@ -138,3 +159,29 @@
     aconfig_declarations: "android.view.inputmethod.flags-aconfig",
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
+
+// Vibrator
+aconfig_declarations {
+    name: "android.os.vibrator.flags-aconfig",
+    package: "android.os.vibrator",
+    srcs: ["core/java/android/os/vibrator/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.os.vibrator.flags-aconfig-java",
+    aconfig_declarations: "android.os.vibrator.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+// Media
+aconfig_declarations {
+    name: "android.media.flags-aconfig",
+    package: "android.media",
+    srcs: ["media/java/android/media/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.media.flags-aconfig-java",
+    aconfig_declarations: "android.media.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index 17076bc..66c1efc 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -419,6 +419,14 @@
      */
     public static final int REASON_SYSTEM_EXEMPT_APP_OP = 327;
 
+    /**
+     * Granted by {@link com.android.server.pm.PackageArchiverService} to the installer responsible
+     * for unarchiving an app.
+     *
+     * @hide
+     */
+    public static final int REASON_PACKAGE_UNARCHIVE = 328;
+
     /** @hide The app requests out-out. */
     public static final int REASON_OPT_OUT_REQUESTED = 1000;
 
@@ -502,6 +510,7 @@
             REASON_ACTIVE_DEVICE_ADMIN,
             REASON_MEDIA_NOTIFICATION_TRANSFER,
             REASON_PACKAGE_INSTALLER,
+            REASON_PACKAGE_UNARCHIVE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ReasonCode {}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2fa31ed..0c61981 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3213,6 +3213,7 @@
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
     method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
     method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
+    method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
   }
@@ -3546,6 +3547,7 @@
     field public static final String ACTION_SHOW_SUSPENDED_APP_DETAILS = "android.intent.action.SHOW_SUSPENDED_APP_DETAILS";
     field @Deprecated public static final String ACTION_SIM_STATE_CHANGED = "android.intent.action.SIM_STATE_CHANGED";
     field public static final String ACTION_SPLIT_CONFIGURATION_CHANGED = "android.intent.action.SPLIT_CONFIGURATION_CHANGED";
+    field public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
     field public static final String ACTION_UPGRADE_SETUP = "android.intent.action.UPGRADE_SETUP";
     field public static final String ACTION_USER_ADDED = "android.intent.action.USER_ADDED";
     field public static final String ACTION_USER_REMOVED = "android.intent.action.USER_REMOVED";
@@ -3795,6 +3797,9 @@
 
   public class PackageArchiver {
     method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+    field public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
+    field public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
   }
 
   public class PackageInfo implements android.os.Parcelable {
diff --git a/core/java/android/app/IUriGrantsManager.aidl b/core/java/android/app/IUriGrantsManager.aidl
index 9e7f2fe..b630d03 100644
--- a/core/java/android/app/IUriGrantsManager.aidl
+++ b/core/java/android/app/IUriGrantsManager.aidl
@@ -39,4 +39,7 @@
     void clearGrantedUriPermissions(in String packageName, int userId);
     ParceledListSlice getUriPermissions(in String packageName, boolean incoming,
             boolean persistedOnly);
+
+    int checkGrantUriPermission_ignoreNonSystem(
+            int sourceUid, String targetPkg, in Uri uri, int modeFlags, int userId);
 }
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
new file mode 100644
index 0000000..afe87de
--- /dev/null
+++ b/core/java/android/app/usage/flags.aconfig
@@ -0,0 +1,9 @@
+package: "android.app.usage"
+
+flag {
+    name: "user_interaction_type_api"
+    namespace: "power_optimization"
+    description: "Feature flag for user interaction event report/query API"
+    bug: "296061232"
+}
+
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 4801d15..be699f4 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -70,6 +70,12 @@
     void close();
 
     /**
+     * Specifies a policy for this virtual device.
+     */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+    void setDevicePolicy(int policyType, int devicePolicy);
+
+    /**
      * Notifies that an audio session being started.
      */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index d13bfd4..2e5c0f7 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -238,6 +238,15 @@
         }
     }
 
+    void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
+            @VirtualDeviceParams.DevicePolicy int devicePolicy) {
+        try {
+            mVirtualDevice.setDevicePolicy(policyType, devicePolicy);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     @NonNull
     VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) {
         try {
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index af41370..923e689 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -19,6 +19,7 @@
 import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -59,6 +60,8 @@
 import android.util.Log;
 import android.view.Surface;
 
+import com.android.internal.util.AnnotationValidations;
+
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -513,6 +516,28 @@
         }
 
         /**
+         * Specifies a policy for this virtual device.
+         *
+         * <p>Policies define the system behavior that may be specific for this virtual device. The
+         * given policy must be able to be changed dynamically during the lifetime of the device.
+         *
+         * @param policyType the type of policy, i.e. which behavior to specify a policy for.
+         * @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
+         *
+         * @see VirtualDeviceParams#POLICY_TYPE_RECENTS
+         */
+        @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
+                @VirtualDeviceParams.DevicePolicy int devicePolicy) {
+            AnnotationValidations.validate(
+                    VirtualDeviceParams.DynamicPolicyType.class, null, policyType);
+            AnnotationValidations.validate(
+                    VirtualDeviceParams.DevicePolicy.class, null, devicePolicy);
+            mVirtualDeviceInternal.setDevicePolicy(policyType, devicePolicy);
+        }
+
+        /**
          * Creates a virtual dpad.
          *
          * @param config the configurations of the virtual dpad.
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index b6d8375..037e814a 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -150,6 +150,17 @@
     public @interface PolicyType {}
 
     /**
+     * Policy types that can be dynamically changed during the virtual device's lifetime.
+     *
+     * @see VirtualDeviceManager.VirtualDevice#setDevicePolicy
+     * @hide
+     */
+    @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_RECENTS})
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+    public @interface DynamicPolicyType {}
+
+    /**
      * Tells the sensor framework how to handle sensor requests from contexts associated with this
      * virtual device, namely the sensors returned by
      * {@link android.hardware.SensorManager#getSensorList}:
@@ -375,6 +386,14 @@
     }
 
     /**
+     * Returns all device policies.
+     * @hide
+     */
+    public @NonNull SparseIntArray getDevicePolicies() {
+        return mDevicePolicies;
+    }
+
+    /**
      * Returns the configurations for all sensors that should be created for this device.
      *
      * @see Builder#addVirtualSensorConfig
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index 39b99c6..057b856 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -6,3 +6,10 @@
   description: "More logs to test flags with"
   bug: "291725823"
 }
+
+flag {
+  name: "dynamic_policy"
+  namespace: "virtual_devices"
+  description: "Enable dynamic policy API"
+  bug: "298401780"
+}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 31f6418..fe7d1e6 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5270,6 +5270,16 @@
     public static final String ACTION_SHOW_FOREGROUND_SERVICE_MANAGER =
             "android.intent.action.SHOW_FOREGROUND_SERVICE_MANAGER";
 
+    /**
+     * Broadcast Action: Sent to the responsible installer of an archived package when unarchival
+     * is requested.
+     *
+     * @see android.content.pm.PackageArchiver
+     * @hide
+     */
+    @SystemApi
+    public static final String ACTION_UNARCHIVE_PACKAGE = "android.intent.action.UNARCHIVE_PACKAGE";
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Standard intent categories (see addCategory()).
diff --git a/core/java/android/content/pm/IPackageArchiverService.aidl b/core/java/android/content/pm/IPackageArchiverService.aidl
index fc471c4..dc6491d 100644
--- a/core/java/android/content/pm/IPackageArchiverService.aidl
+++ b/core/java/android/content/pm/IPackageArchiverService.aidl
@@ -23,4 +23,7 @@
 
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})")
     void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
+    void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle);
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/PackageArchiver.java b/core/java/android/content/pm/PackageArchiver.java
index d739d50..b065231 100644
--- a/core/java/android/content/pm/PackageArchiver.java
+++ b/core/java/android/content/pm/PackageArchiver.java
@@ -42,6 +42,26 @@
 @SystemApi
 public class PackageArchiver {
 
+    /**
+     * Extra field for the package name of a package that is requested to be unarchived. Sent as
+     * part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} intent.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_UNARCHIVE_PACKAGE_NAME =
+            "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
+
+    /**
+     * If true, the requestor of the unarchival has specified that the app should be unarchived
+     * for {@link android.os.UserHandle#ALL}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_UNARCHIVE_ALL_USERS =
+            "android.content.pm.extra.UNARCHIVE_ALL_USERS";
+
     private final Context mContext;
     private final IPackageArchiverService mService;
 
@@ -58,7 +78,7 @@
      *
      * @param statusReceiver Callback used to notify when the operation is completed.
      * @throws NameNotFoundException If {@code packageName} isn't found or not available to the
-     *                               caller.
+     *                               caller or isn't archived.
      * @hide
      */
     @RequiresPermission(anyOf = {
@@ -76,4 +96,34 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Requests to unarchive a currently archived package.
+     *
+     * <p> Sends a request to unarchive an app to the responsible installer. The installer is
+     * determined by {@link InstallSourceInfo#getUpdateOwnerPackageName()}, or
+     * {@link InstallSourceInfo#getInstallingPackageName()} if the former value is null.
+     *
+     * <p> The installation will happen asynchronously and can be observed through
+     * {@link android.content.Intent#ACTION_PACKAGE_ADDED}.
+     *
+     * @throws NameNotFoundException If {@code packageName} isn't found or not visible to the
+     *                               caller or if the package has no installer on the device
+     *                               anymore to unarchive it.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.INSTALL_PACKAGES,
+            Manifest.permission.REQUEST_INSTALL_PACKAGES})
+    @SystemApi
+    public void requestUnarchive(@NonNull String packageName)
+            throws NameNotFoundException {
+        try {
+            mService.requestUnarchive(packageName, mContext.getPackageName(), mContext.getUser());
+        } catch (ParcelableException e) {
+            e.maybeRethrow(NameNotFoundException.class);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index c01664e..8be4c58 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -237,7 +237,7 @@
                 mNavigationBarFrame.setOnApplyWindowInsetsListener((view, insets) -> {
                     if (mNavigationBarFrame != null) {
                         boolean visible = insets.isVisible(captionBar());
-                        mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+                        mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.GONE);
                     }
                     return view.onApplyWindowInsets(insets);
                 });
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index 6275352..f30dd20 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -36,4 +36,10 @@
     void vibrate(int uid, int displayId, String opPkg, in CombinedVibration vibration,
             in VibrationAttributes attributes, String reason, IBinder token);
     void cancelVibrate(int usageFilter, IBinder token);
+
+    // Async oneway APIs.
+    // There is no order guarantee with respect to the two-way APIs above like
+    // vibrate/isVibrating/cancel.
+    oneway void performHapticFeedback(int uid, int displayId, String opPkg, int constant,
+            boolean always, String reason, IBinder token);
 }
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 1cd0f3b..04c257b 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -206,6 +206,15 @@
     }
 
     @Override
+    public void performHapticFeedback(int constant, boolean always, String reason) {
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager.");
+            return;
+        }
+        mVibratorManager.performHapticFeedback(constant, always, reason);
+    }
+
+    @Override
     public void cancel() {
         if (mVibratorManager == null) {
             Log.w(TAG, "Failed to cancel vibrate; no vibrator manager.");
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index 284b246..ee90834 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -145,6 +145,21 @@
     }
 
     @Override
+    public void performHapticFeedback(int constant, boolean always, String reason) {
+        if (mService == null) {
+            Log.w(TAG, "Failed to perform haptic feedback; no vibrator manager service.");
+            return;
+        }
+        try {
+            mService.performHapticFeedback(
+                    Process.myUid(), mContext.getAssociatedDisplayId(), mPackageName, constant,
+                    always, reason, mToken);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to perform haptic feedback.", e);
+        }
+    }
+
+    @Override
     public void cancel() {
         cancelVibration(VibrationAttributes.USAGE_FILTER_MATCH_ALL);
     }
@@ -228,6 +243,11 @@
         }
 
         @Override
+        public void performHapticFeedback(int effectId, boolean always, String reason) {
+            SystemVibratorManager.this.performHapticFeedback(effectId, always, reason);
+        }
+
+        @Override
         public void cancel() {
             SystemVibratorManager.this.cancel();
         }
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index aafa501..99c9925 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -510,6 +510,28 @@
             String reason, @NonNull VibrationAttributes attributes);
 
     /**
+     * Performs a haptic feedback.
+     *
+     * <p>A haptic feedback is a short vibration feedback. The type of feedback is identified via
+     * the {@code constant}, which should be one of the effect constants provided in
+     * {@link HapticFeedbackConstants}. The haptic feedback provided for a given effect ID is
+     * consistent across all usages on the same device.
+     *
+     * @param constant the ID for the haptic feedback. This should be one of the constants defined
+     *          in {@link HapticFeedbackConstants}.
+     * @param always {@code true} if the haptic feedback should be played regardless of the user
+     *          vibration intensity settings applicable to the corresponding vibration.
+     *          {@code false} if the vibration for the haptic feedback should respect the applicable
+     *          vibration intensity settings.
+     * @param reason the reason for this haptic feedback.
+     *
+     * @hide
+     */
+    public void performHapticFeedback(int constant, boolean always, String reason) {
+        Log.w(TAG, "performHapticFeedback is not supported");
+    }
+
+    /**
      * Query whether the vibrator natively supports the given effects.
      *
      * <p>If an effect is not supported, the system may still automatically fall back to playing
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index f506ef8..e0b6a9f 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -35,7 +35,8 @@
 public abstract class VibratorManager {
     private static final String TAG = "VibratorManager";
 
-    private final String mPackageName;
+    /** @hide */
+    protected final String mPackageName;
 
     /**
      * @hide to prevent subclassing from outside of the framework
@@ -137,6 +138,21 @@
             String reason, @Nullable VibrationAttributes attributes);
 
     /**
+     * Performs a haptic feedback.
+     *
+     * @param constant the ID of the requested haptic feedback. Should be one of the constants
+     *          defined in {@link HapticFeedbackConstants}.
+     * @param always {@code true} if the haptic feedback should be played regardless of the user
+     *          vibration intensity settings applicable to the corresponding vibration.
+     *          {@code false} otherwise.
+     * @param reason the reason for this haptic feedback.
+     * @hide
+     */
+    public void performHapticFeedback(int constant, boolean always, String reason) {
+        Log.w(TAG, "performHapticFeedback is not supported");
+    }
+
+    /**
      * Turn all the vibrators off.
      */
     @RequiresPermission(android.Manifest.permission.VIBRATE)
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
new file mode 100644
index 0000000..361e244
--- /dev/null
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.os.vibrator"
+
+flag {
+    namespace: "vibrator"
+    name: "use_vibrator_haptic_feedback"
+    description: "Enables performHapticFeedback to directly use the vibrator service instead of going through the window session"
+    bug: "295459081"
+}
\ No newline at end of file
diff --git a/core/java/android/text/flags/phrase_strict_fallback.aconfig b/core/java/android/text/flags/phrase_strict_fallback.aconfig
new file mode 100644
index 0000000..c67a21b
--- /dev/null
+++ b/core/java/android/text/flags/phrase_strict_fallback.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.text.flags"
+
+flag {
+  name: "phrase_strict_fallback"
+  namespace: "text"
+  description: "Feature flag for automatic fallback from phrase based line break to strict line break."
+  bug: "281970875"
+}
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 5fe2aa1..12527e9 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -254,7 +254,6 @@
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "true");
         DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "true");
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");
-        DEFAULT_FLAGS.put("settings_press_hold_nav_handle_to_search", "false");
         // TODO: b/298454866 Replace with Trunk Stable Feature Flag
         DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false");
     }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 9a4cb72..30fd2cf 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -105,6 +105,8 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.os.Vibrator;
+import android.os.vibrator.Flags;
 import android.sysprop.DisplayProperties;
 import android.text.InputType;
 import android.text.TextUtils;
@@ -5411,6 +5413,9 @@
      */
     private PointerIcon mMousePointerIcon;
 
+    /** Vibrator for haptic feedback. */
+    private Vibrator mVibrator;
+
     /**
      * @hide
      */
@@ -27758,8 +27763,24 @@
                 && !isHapticFeedbackEnabled()) {
             return false;
         }
-        return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant,
-                (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0);
+
+        final boolean always = (flags & HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING) != 0;
+        if (Flags.useVibratorHapticFeedback()) {
+            if (!mAttachInfo.canPerformHapticFeedback()) {
+                return false;
+            }
+            getSystemVibrator().performHapticFeedback(
+                    feedbackConstant, always, "View#performHapticFeedback");
+            return true;
+        }
+        return mAttachInfo.mRootCallbacks.performHapticFeedback(feedbackConstant, always);
+    }
+
+    private Vibrator getSystemVibrator() {
+        if (mVibrator != null) {
+            return mVibrator;
+        }
+        return mVibrator = mContext.getSystemService(Vibrator.class);
     }
 
     /**
@@ -31239,6 +31260,11 @@
             return events;
         }
 
+        private boolean canPerformHapticFeedback() {
+            return mSession != null
+                    && (mDisplay.getFlags() & Display.FLAG_TOUCH_FEEDBACK_DISABLED) == 0;
+        }
+
         @Nullable
         ScrollCaptureInternal getScrollCaptureInternal() {
             if (mScrollCaptureInternal != null) {
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index f67a61b..61470f2 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -28,7 +28,13 @@
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UiThread;
+import android.app.UriGrantsManager;
+import android.content.ContentProvider;
+import android.content.Intent;
 import android.graphics.RectF;
+import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.CancellationSignalBeamer;
@@ -37,6 +43,7 @@
 import android.os.Looper;
 import android.os.ResultReceiver;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 import android.view.KeyEvent;
@@ -1143,7 +1150,22 @@
     public void commitContent(InputConnectionCommandHeader header,
             InputContentInfo inputContentInfo, int flags, Bundle opts,
             AndroidFuture future /* T=Boolean */) {
+        final int imeUid = Binder.getCallingUid();
         dispatchWithTracing("commitContent", future, () -> {
+            // Check if the originator IME has the right permissions
+            try {
+                final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(
+                        inputContentInfo.getContentUri(), UserHandle.getUserId(imeUid));
+                final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(
+                        inputContentInfo.getContentUri());
+                UriGrantsManager.getService().checkGrantUriPermission_ignoreNonSystem(imeUid, null,
+                        contentUriWithoutUserId, Intent.FLAG_GRANT_READ_URI_PERMISSION,
+                        contentUriOwnerUserId);
+            } catch (Exception e) {
+                Log.w(TAG, "commitContent with invalid Uri permission from IME:", e);
+                return false;
+            }
+
             if (header.mSessionId != mCurrentSessionId.get()) {
                 return false;  // cancelled
             }
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 6523fff..f5b81b0 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -50,6 +50,7 @@
 import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
+import android.view.PointerIcon;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
@@ -3139,4 +3140,15 @@
         if (result == null) return super.onApplyWindowInsets(insets);
         return result;
     }
+
+    @Override
+    @Nullable
+    public PointerIcon onResolvePointerIcon(@NonNull MotionEvent event, int pointerIndex) {
+        PointerIcon icon =
+                mProvider.getViewDelegate().onResolvePointerIcon(event, pointerIndex);
+        if (icon != null) {
+            return icon;
+        }
+        return super.onResolvePointerIcon(event, pointerIndex);
+    }
 }
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 26579c5d..ca423e0 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -39,6 +39,7 @@
 import android.view.DragEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
+import android.view.PointerIcon;
 import android.view.View;
 import android.view.ViewGroup.LayoutParams;
 import android.view.WindowInsets;
@@ -496,6 +497,15 @@
         default WindowInsets onApplyWindowInsets(@Nullable WindowInsets insets) {
             return null;
         }
+
+        /**
+         * @hide Only used by WebView.
+         */
+        @SuppressWarnings("unused")
+        @Nullable
+        default PointerIcon onResolvePointerIcon(@NonNull MotionEvent event, int pointerIndex) {
+            return null;
+        }
     }
 
     interface ScrollDelegate {
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 560e41b..b8d251f 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -8,3 +8,10 @@
     description: "Whether the feature to sync different window-related config updates is enabled"
     bug: "260873529"
 }
+
+flag {
+    namespace: "windowing_sdk"
+    name: "activity_embedding_overlay_presentation_flag"
+    description: "Whether the overlay presentation feature is enabled"
+    bug: "243518738"
+}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 03480e4..302c7fa 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5480,6 +5480,13 @@
          of known compatibility issues. -->
     <string-array name="config_highRefreshRateBlacklist"></string-array>
 
+    <!-- The list of packages to automatically opt in to refresh rate suppressing by small area
+    detection. Format of this array should be packageName:threshold and threshold value should
+     be between 0 to 1-->
+    <string-array name="config_smallAreaDetectionAllowlist" translatable="false">
+        <!-- Add packages:threshold here -->
+    </string-array>
+
     <!-- The list of packages to force slowJpegMode for Apps using Camera API1 -->
     <string-array name="config_forceSlowJpegModeList" translatable="false">
         <!-- Add packages here -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7ea5974..02209a7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4288,6 +4288,8 @@
   <java-symbol type="array" name="config_highRefreshRateBlacklist" />
   <java-symbol type="array" name="config_forceSlowJpegModeList" />
 
+  <java-symbol type="array" name="config_smallAreaDetectionAllowlist" />
+
   <java-symbol type="layout" name="chooser_dialog" />
   <java-symbol type="layout" name="chooser_dialog_item" />
   <java-symbol type="drawable" name="chooser_dialog_background" />
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index a41fb64..0778311 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -67,7 +67,6 @@
 import android.window.WindowContextInfo;
 import android.window.WindowTokenClientController;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
@@ -254,15 +253,9 @@
 
             // Execute a local relaunch item with current scaled config (e.g. simulate recreate),
             // the config should not be scaled again.
-            final Configuration currentConfig = activity.getResources().getConfiguration();
-            final ClientTransaction localTransaction =
-                    newTransaction(activityThread, activity.getActivityToken());
-            localTransaction.addCallback(ActivityRelaunchItem.obtain(
-                    null /* pendingResults */, null /* pendingIntents */, 0 /* configChanges */,
-                    new MergedConfiguration(currentConfig, currentConfig),
-                    true /* preserveWindow */));
             InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                    () -> activityThread.executeTransaction(localTransaction));
+                    () -> activityThread.executeTransaction(
+                            newRelaunchResumeTransaction(activity)));
 
             assertScreenScale(scale, activity, originalActivityConfig, originalActivityMetrics);
         } finally {
@@ -630,7 +623,6 @@
         });
     }
 
-    @FlakyTest(bugId = 298331121)
     @Test
     public void testHandleConfigurationChanged_DoesntOverrideActivityConfig() {
         final TestActivity activity = mActivityTestRule.launchActivity(new Intent());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 9aac694..0479576 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -103,7 +103,7 @@
         default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {}
         /** Whether this task listener supports compat UI. */
         default boolean supportCompatUI() {
-            // All TaskListeners should support compat UI except PIP.
+            // All TaskListeners should support compat UI except PIP and StageCoordinator.
             return true;
         }
         /** Attaches a child window surface to the task surface. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index 72702e7..b828aac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Nullable;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.util.Slog;
 import android.view.IDisplayChangeWindowCallback;
 import android.view.IDisplayChangeWindowController;
@@ -40,6 +41,7 @@
  */
 public class DisplayChangeController {
     private static final String TAG = DisplayChangeController.class.getSimpleName();
+    private static final String HANDLE_DISPLAY_CHANGE_TRACE_TAG = "HandleRemoteDisplayChange";
 
     private final ShellExecutor mMainExecutor;
     private final IWindowManager mWmService;
@@ -81,9 +83,15 @@
     /** Query all listeners for changes that should happen on display change. */
     void dispatchOnDisplayChange(WindowContainerTransaction outWct, int displayId,
             int fromRotation, int toRotation, DisplayAreaInfo newDisplayAreaInfo) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+            Trace.beginSection("dispatchOnDisplayChange");
+        }
         for (OnDisplayChangingListener c : mDisplayChangeListener) {
             c.onDisplayChange(displayId, fromRotation, toRotation, newDisplayAreaInfo, outWct);
         }
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+            Trace.endSection();
+        }
     }
 
     private void onDisplayChange(int displayId, int fromRotation, int toRotation,
@@ -94,6 +102,10 @@
             callback.continueDisplayChange(t);
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to continue handling display change", e);
+        } finally {
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+                Trace.endAsyncSection(HANDLE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+            }
         }
     }
 
@@ -103,6 +115,9 @@
         @Override
         public void onDisplayChange(int displayId, int fromRotation, int toRotation,
                 DisplayAreaInfo newDisplayAreaInfo, IDisplayChangeWindowCallback callback) {
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+                Trace.beginAsyncSection(HANDLE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+            }
             mMainExecutor.execute(() -> DisplayChangeController.this
                     .onDisplayChange(displayId, fromRotation, toRotation,
                             newDisplayAreaInfo, callback));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
index cbff464..77aefc8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -208,7 +208,7 @@
     }
 
     private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) {
-        return  taskInfo.topActivityEligibleForUserAspectRatioButton
+        return taskInfo.topActivityEligibleForUserAspectRatioButton
                 && (taskInfo.topActivityBoundsLetterboxed
                     || taskInfo.isUserFullscreenOverrideEnabled);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 842b1bf..94fa485 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -228,6 +228,15 @@
     private final Toast mSplitUnsupportedToast;
     private SplitRequest mSplitRequest;
 
+    /**
+     * Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support
+     * CompatUI layouts. CompatUI is handled separately by MainStage and SideStage.
+     */
+    @Override
+    public boolean supportCompatUI() {
+        return false;
+    }
+
     class SplitRequest {
         @SplitPosition
         int mActivatePosition;
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index 434b008..eb650ca 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -126,6 +126,7 @@
         "flickertestapplib",
         "flickerlib",
         "flickerlib-helpers",
+        "flickerlib-trace_processor_shell",
         "platform-test-annotations",
         "wm-flicker-common-app-helpers",
         "wm-flicker-common-assertions",
diff --git a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
index 6a87de4..ae130b8 100644
--- a/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/manifests/AndroidManifest.xml
@@ -45,9 +45,13 @@
     <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
     <!-- Enable bubble notification-->
     <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+    <!-- Allow the test to connect to perfetto trace processor -->
+    <uses-permission android:name="android.permission.INTERNET"/>
 
-    <!-- Allow the test to write directly to /sdcard/ -->
-    <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
+    <!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
+    <application android:requestLegacyExternalStorage="true"
+                 android:networkSecurityConfig="@xml/network_security_config"
+                 android:largeHeap="true">
         <uses-library android:name="android.test.runner"/>
 
         <service android:name=".NotificationListener"
diff --git a/libs/WindowManager/Shell/tests/flicker/res/xml/network_security_config.xml b/libs/WindowManager/Shell/tests/flicker/res/xml/network_security_config.xml
new file mode 100644
index 0000000..4bd9ca0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/res/xml/network_security_config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<network-security-config>
+    <domain-config cleartextTrafficPermitted="true">
+        <domain includeSubdomains="true">localhost</domain>
+    </domain-config>
+</network-security-config>
diff --git a/media/java/android/media/flags.aconfig b/media/java/android/media/flags.aconfig
new file mode 100644
index 0000000..8567a3b
--- /dev/null
+++ b/media/java/android/media/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.media"
+
+flag {
+    name: "haptics_customization_enabled"
+    namespace: "media"
+    description: "Enables the haptics customization feature"
+    bug: "241918098"
+}
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 3d35bad..5dcb9d2 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -8,7 +8,6 @@
 }
 
 android_library {
-
     name: "SettingsLib",
 
     static_libs: [
@@ -26,44 +25,20 @@
         "iconloader",
 
         "WifiTrackerLibRes",
-        "SettingsLibHelpUtils",
-        "SettingsLibRestrictedLockUtils",
-        "SettingsLibActionBarShadow",
-        "SettingsLibAppPreference",
-        "SettingsLibSearchWidget",
-        "SettingsLibSettingsSpinner",
-        "SettingsLibIllustrationPreference",
-        "SettingsLibLayoutPreference",
-        "SettingsLibMainSwitchPreference",
-        "SettingsLibActionButtonsPreference",
-        "SettingsLibEntityHeaderWidgets",
-        "SettingsLibBarChartPreference",
-        "SettingsLibProgressBar",
-        "SettingsLibAdaptiveIcon",
-        "SettingsLibRadioButtonPreference",
-        "SettingsLibSelectorWithWidgetPreference",
-        "SettingsLibDisplayUtils",
-        "SettingsLibUtils",
-        "SettingsLibEmergencyNumber",
-        "SettingsLibTopIntroPreference",
-        "SettingsLibBannerMessagePreference",
-        "SettingsLibFooterPreference",
-        "SettingsLibUsageProgressBarPreference",
-        "SettingsLibCollapsingToolbarBaseActivity",
-        "SettingsLibTwoTargetPreference",
-        "SettingsLibSettingsTransition",
-        "SettingsLibButtonPreference",
         "SettingsLibDeviceStateRotationLock",
-        "SettingsLibProfileSelector",
+        "SettingsLibDisplayUtils",
+        "SettingsLibEmergencyNumber",
+        "SettingsLibSearchWidget",
+        "SettingsLibUtils",
+        "SettingsLibWidget",
         "setupdesign",
         "zxing-core-1.7",
         "androidx.room_room-runtime",
         "settingslib_flags_lib",
-
     ],
 
     plugins: ["androidx.room_room-compiler-plugin"],
-
+    use_resource_processor: true,
     resource_dirs: ["res"],
 
     srcs: [
@@ -72,6 +47,43 @@
     ],
 }
 
+// Group all the libraries with namespace "com.android.settingslib.widget", to allow SettingsLib to
+// set use_resource_processor = true.
+// We can remove SettingsLibWidget when all these libraries have its own namespace.
+android_library {
+    name: "SettingsLibWidget",
+    visibility: ["//visibility:private"],
+    manifest: "AndroidManifest-SettingsLibWidget.xml",
+    static_libs: [
+        "SettingsLibActionBarShadow",
+        "SettingsLibActionButtonsPreference",
+        "SettingsLibAdaptiveIcon",
+        "SettingsLibAppPreference",
+        "SettingsLibBannerMessagePreference",
+        "SettingsLibBarChartPreference",
+        "SettingsLibButtonPreference",
+        "SettingsLibCollapsingToolbarBaseActivity",
+        "SettingsLibEntityHeaderWidgets",
+        "SettingsLibFooterPreference",
+        "SettingsLibHelpUtils",
+        "SettingsLibIllustrationPreference",
+        "SettingsLibLayoutPreference",
+        "SettingsLibMainSwitchPreference",
+        "SettingsLibProfileSelector",
+        "SettingsLibProgressBar",
+        "SettingsLibRadioButtonPreference",
+        "SettingsLibRestrictedLockUtils",
+        "SettingsLibSelectorWithWidgetPreference",
+        "SettingsLibSettingsSpinner",
+        "SettingsLibSettingsTransition",
+        "SettingsLibTopIntroPreference",
+        "SettingsLibTwoTargetPreference",
+        "SettingsLibUsageProgressBarPreference",
+    ],
+
+    resource_dirs: [],
+}
+
 // NOTE: Keep this module in sync with ./common.mk
 java_defaults {
     name: "SettingsLibDefaults",
diff --git a/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml b/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml
new file mode 100644
index 0000000..38a7d6a
--- /dev/null
+++ b/packages/SettingsLib/AndroidManifest-SettingsLibWidget.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+-->
+
+<manifest package="com.android.settingslib.widget" />
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index 90a723f..b77368a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -253,6 +253,7 @@
             hideTitleSemantics = false,
             navigationIcon = navigationIcon,
             actions = actionsRow,
+            titleScaleDisabled = false,
         )
     }
 }
@@ -426,6 +427,7 @@
  * accessibility services at the same time, when animating between collapsed / expanded states.
  * @param navigationIcon a navigation icon [Composable]
  * @param actions actions [Composable]
+ * @param titleScaleDisabled whether the title font scaling is disabled. Default is disabled.
  */
 @Composable
 private fun TopAppBarLayout(
@@ -443,6 +445,7 @@
     hideTitleSemantics: Boolean,
     navigationIcon: @Composable () -> Unit,
     actions: @Composable () -> Unit,
+    titleScaleDisabled: Boolean = true,
 ) {
     Layout(
         {
@@ -466,9 +469,12 @@
                 ProvideTextStyle(value = titleTextStyle) {
                     CompositionLocalProvider(
                         LocalContentColor provides titleContentColor,
-                        // Disable the title font scaling by only passing the density but not the
-                        // font scale.
-                        LocalDensity provides Density(density = LocalDensity.current.density),
+                        LocalDensity provides with(LocalDensity.current) {
+                          Density(
+                              density = density,
+                              fontScale = if (titleScaleDisabled) 1f else fontScale,
+                          )
+                        },
                         content = title
                     )
                 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index d437e35..696e877 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -18,6 +18,7 @@
 
 import androidx.activity.compose.BackHandler
 import androidx.appcompat.R
+import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.RowScope
@@ -96,7 +97,8 @@
             Modifier
                 .padding(paddingValues.horizontalValues())
                 .padding(top = paddingValues.calculateTopPadding())
-                .fillMaxSize(),
+                .focusable()
+                .fillMaxSize()
         ) {
             content(
                 paddingValues.calculateBottomPadding(),
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index af06d73..b1d1ea5e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -126,10 +126,10 @@
             switchSummary = isChecked()
                     ? getUpdatableEnterpriseString(
                             getContext(), ENABLED_BY_ADMIN_SWITCH_SUMMARY,
-                            R.string.enabled_by_admin)
+                            com.android.settingslib.widget.R.string.enabled_by_admin)
                     : getUpdatableEnterpriseString(
                             getContext(), DISABLED_BY_ADMIN_SWITCH_SUMMARY,
-                            R.string.disabled_by_admin);
+                            com.android.settingslib.widget.R.string.disabled_by_admin);
         } else {
             switchSummary = mRestrictedSwitchSummary;
         }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 6eb2f38..96bb4b5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1776,7 +1776,8 @@
             final int userId = UserHandle.getUserId(this.info.uid);
             if (UserManager.get(context).isManagedProfile(userId)) {
                 this.labelDescription = context.getString(
-                        com.android.settingslib.R.string.accessibility_work_profile_app_description,
+                        com.android.settingslib.utils.R
+                                .string.accessibility_work_profile_app_description,
                         this.label);
             } else {
                 this.labelDescription = this.label;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java
index cb4eba4..f5257b0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BroadcastDialog.java
@@ -46,7 +46,8 @@
         View layout = View.inflate(mContext, R.layout.broadcast_dialog, null);
         final Window window = getWindow();
         window.setContentView(layout);
-        window.setWindowAnimations(R.style.Theme_AlertDialog_SettingsLib);
+        window.setWindowAnimations(
+                com.android.settingslib.widget.R.style.Theme_AlertDialog_SettingsLib);
 
         TextView title = layout.findViewById(R.id.dialog_title);
         TextView subTitle = layout.findViewById(R.id.dialog_subtitle);
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
index 1f4cafce..d53c3a7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java
@@ -153,7 +153,7 @@
         }
         final ImageView icon = holder.itemView.findViewById(android.R.id.icon);
         final int iconSize = getContext().getResources().getDimensionPixelSize(
-                R.dimen.secondary_app_icon_size);
+                com.android.settingslib.widget.R.dimen.secondary_app_icon_size);
         if (icon != null && iconSize > 0) {
             ViewGroup.LayoutParams params = icon.getLayoutParams();
             params.height = iconSize;
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
index ebdfbea..251cd36 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/ConversationIconFactory.java
@@ -32,7 +32,6 @@
 import android.util.Log;
 
 import com.android.launcher3.icons.BaseIconFactory;
-import com.android.settingslib.R;
 import com.android.settingslib.Utils;
 
 /**
@@ -81,7 +80,7 @@
         mPackageManager = pm;
         mIconDrawableFactory = iconDrawableFactory;
         mImportantConversationColor = context.getResources().getColor(
-                R.color.important_conversation, null);
+                com.android.launcher3.icons.R.color.important_conversation, null);
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index 21eb35a..2d6f058 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -167,7 +167,8 @@
         ImageView frictionImageView = (ImageView) view.findViewById(R.id.friction_icon);
         bindFrictionImage(frictionImageView);
 
-        final View divider = view.findViewById(R.id.two_target_divider);
+        final View divider =
+                view.findViewById(com.android.settingslib.widget.R.id.two_target_divider);
         divider.setVisibility(shouldShowDivider() ? View.VISIBLE : View.INVISIBLE);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index c45d774..015356e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -331,7 +331,8 @@
                 return;
             } else if (!isDefaultNetwork && mDefaultNetworkCapabilities != null
                     && mDefaultNetworkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
-                statusLabel = mContext.getString(R.string.wifi_connected_low_quality);
+                statusLabel = mContext.getString(
+                        com.android.wifitrackerlib.R.string.wifi_connected_low_quality);
                 return;
             }
         }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java
index 74c2fc8..32a16716 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java
@@ -54,7 +54,7 @@
         mPreference = new PrimarySwitchPreference(mContext);
         LayoutInflater inflater = LayoutInflater.from(mContext);
         mHolder = PreferenceViewHolder.createInstanceForTests(inflater.inflate(
-                com.android.settingslib.R.layout.preference_two_target, null));
+                com.android.settingslib.widget.R.layout.preference_two_target, null));
         mWidgetView = mHolder.itemView.findViewById(android.R.id.widget_frame);
         inflater.inflate(R.layout.preference_widget_primary_switch, mWidgetView, true);
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
index 06343f5..ff84dc9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayoutTest.java
@@ -55,7 +55,8 @@
     @Test
     public void onCreate_userAddedChildViewsBeMovedToContentFrame() {
         CollapsingCoordinatorLayout layout = mActivity.getCollapsingCoordinatorLayout();
-        View contentFrameView = layout.findViewById(R.id.content_frame);
+        View contentFrameView =
+                layout.findViewById(com.android.settingslib.widget.R.id.content_frame);
 
         TextView textView = contentFrameView.findViewById(R.id.text_hello_world);
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
index 471dac0..8246aff 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/HideNonSystemOverlayMixinTest.java
@@ -29,8 +29,6 @@
 import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
 
-import com.android.settingslib.R;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -95,7 +93,7 @@
         @Override
         protected void onCreate(@Nullable Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
-            setTheme(R.style.Theme_AppCompat);
+            setTheme(androidx.appcompat.R.style.Theme_AppCompat);
             getLifecycle().addObserver(new HideNonSystemOverlayMixin(this));
         }
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java
index 7b08fee..2f8967e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FinancedDeviceActionDisabledByAdminControllerTest.java
@@ -30,8 +30,6 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
-import com.android.settingslib.R;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -51,7 +49,7 @@
 
     @Before
     public void setUp() {
-        mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
+        mActivity.setTheme(androidx.appcompat.R.style.Theme_AppCompat_DayNight);
 
         mController.initialize(mTestUtils.createLearnMoreButtonLauncher());
         mController.updateEnforcedAdmin(ENFORCED_ADMIN, ENFORCEMENT_ADMIN_USER_ID);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
index 7b88566..f168122 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/ManagedDeviceActionDisabledByAdminControllerTest.java
@@ -33,8 +33,6 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
-import com.android.settingslib.R;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -57,7 +55,7 @@
 
     @Before
     public void setUp() {
-        mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
+        mActivity.setTheme(androidx.appcompat.R.style.Theme_AppCompat_DayNight);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
index 66a2ea6..6831222 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/CreateUserDialogControllerTest.java
@@ -62,7 +62,7 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mActivity = spy(ActivityController.of(new FragmentActivity()).get());
-        mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
+        mActivity.setTheme(androidx.appcompat.R.style.Theme_AppCompat_DayNight);
         mUnderTest = new TestCreateUserDialogController();
         mPhotoRestrictedByBase = false;
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
index f595cd3..a95257a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/users/EditUserInfoControllerTest.java
@@ -99,7 +99,7 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mActivity = spy(ActivityController.of(new FragmentActivity()).get());
-        mActivity.setTheme(R.style.Theme_AppCompat_DayNight);
+        mActivity.setTheme(androidx.appcompat.R.style.Theme_AppCompat_DayNight);
         mController = new TestEditUserInfoController();
         mPhotoRestrictedByBase = false;
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java
index 6cbae05..10862403 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveIconTest.java
@@ -105,15 +105,15 @@
 
         icon.setBackgroundColor(mContext, tile);
 
-        assertThat(icon.mBackgroundColor)
-                .isEqualTo(mContext.getColor(R.color.homepage_generic_icon_background));
+        assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor(
+                com.android.settingslib.widget.R.color.homepage_generic_icon_background));
     }
 
     @Test
     public void onBindTile_externalTileWithBackgroundColorHint_shouldUpdateIcon() {
         final Tile tile = spy(new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE));
         mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON_BACKGROUND_HINT,
-                R.color.bt_outline_color);
+                com.android.settingslib.widget.R.color.bt_outline_color);
         doReturn(Icon.createWithResource(mContext, R.drawable.ic_system_update))
                 .when(tile).getIcon(mContext);
 
@@ -121,8 +121,8 @@
                 new AdaptiveIcon(mContext, new ColorDrawable(Color.BLACK));
         icon.setBackgroundColor(mContext, tile);
 
-        assertThat(icon.mBackgroundColor)
-                .isEqualTo(mContext.getColor(R.color.bt_outline_color));
+        assertThat(icon.mBackgroundColor).isEqualTo(mContext.getColor(
+                com.android.settingslib.widget.R.color.bt_outline_color));
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java
index 71d55bc..b2bc53d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/AdaptiveOutlineDrawableTest.java
@@ -21,8 +21,6 @@
 import android.content.res.Resources;
 import android.graphics.Paint;
 
-import com.android.settingslib.R;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
index 049c90e..a26f200 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
@@ -58,14 +58,15 @@
     @Test
     public void setLearnMoreText_shouldSetAsTextInLearnMore() {
         final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(
-                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null));
+                LayoutInflater.from(mContext)
+                        .inflate(com.android.settingslib.widget.R.layout.preference_footer, null));
         mFooterPreference.setLearnMoreText("Custom learn more");
         mFooterPreference.setLearnMoreAction(view -> { /* do nothing */ } /* listener */);
 
         mFooterPreference.onBindViewHolder(holder);
 
         assertThat(((TextView) holder.findViewById(
-                R.id.settingslib_learn_more)).getText().toString())
+                com.android.settingslib.widget.R.id.settingslib_learn_more)).getText().toString())
                 .isEqualTo("Custom learn more");
     }
 
@@ -94,8 +95,9 @@
     @Test
     public void onBindViewHolder_whenTitleIsNull_shouldNotRaiseNpe() {
         PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
-                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
-        when(viewHolder.findViewById(R.id.title)).thenReturn(null);
+                LayoutInflater.from(mContext)
+                        .inflate(com.android.settingslib.widget.R.layout.preference_footer, null)));
+        when(viewHolder.findViewById(androidx.core.R.id.title)).thenReturn(null);
 
         Throwable actualThrowable = null;
         try {
@@ -110,8 +112,10 @@
     @Test
     public void onBindViewHolder_whenLearnMoreIsNull_shouldNotRaiseNpe() {
         PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
-                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
-        when(viewHolder.findViewById(R.id.settingslib_learn_more)).thenReturn(null);
+                LayoutInflater.from(mContext)
+                        .inflate(com.android.settingslib.widget.R.layout.preference_footer, null)));
+        when(viewHolder.findViewById(com.android.settingslib.widget.R.id.settingslib_learn_more))
+                .thenReturn(null);
 
         Throwable actualThrowable = null;
         try {
@@ -126,7 +130,8 @@
     @Test
     public void onBindViewHolder_whenIconFrameIsNull_shouldNotRaiseNpe() {
         PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
-                LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
+                LayoutInflater.from(mContext)
+                        .inflate(com.android.settingslib.widget.R.layout.preference_footer, null)));
         when(viewHolder.findViewById(R.id.icon_frame)).thenReturn(null);
 
         Throwable actualThrowable = null;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java
index aaec909..23b4c2a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/TwoTargetPreferenceTest.java
@@ -32,8 +32,6 @@
 
 import androidx.preference.PreferenceViewHolder;
 
-import com.android.settingslib.R;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 0d7d9cc..017ac60 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -446,7 +446,9 @@
             for (key in listOf(".blue600", ".blue400")) {
                 addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
                     PorterDuffColorFilter(
-                        context.getColor(com.android.settingslib.R.color.settingslib_color_blue400),
+                        context.getColor(
+                            com.android.settingslib.color.R.color.settingslib_color_blue400
+                        ),
                         PorterDuff.Mode.SRC_ATOP
                     )
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
index 970b475..3ff1f09 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprint.kt
@@ -31,7 +31,7 @@
     defaultCommunalWidgetSection: DefaultCommunalWidgetSection,
 ) : KeyguardBlueprint {
     override val id: String = COMMUNAL
-    override val sections: Array<KeyguardSection> = arrayOf(defaultCommunalWidgetSection)
+    override val sections: Set<KeyguardSection> = setOf(defaultCommunalWidgetSection)
 
     companion object {
         const val COMMUNAL = "communal"
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalWidgetSection.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalWidgetSection.kt
index 4fb9384..8640c97 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalWidgetSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/view/layout/sections/DefaultCommunalWidgetSection.kt
@@ -42,9 +42,12 @@
     private val communalWidgetViewModel: CommunalWidgetViewModel,
     private val communalWidgetViewAdapter: CommunalWidgetViewAdapter,
     private val keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
-) : KeyguardSection {
+) : KeyguardSection() {
     private val widgetAreaViewId = R.id.communal_widget_wrapper
-    override fun addViews(constraintLayout: ConstraintLayout) {
+
+    override fun addViews(constraintLayout: ConstraintLayout) {}
+
+    override fun bindData(constraintLayout: ConstraintLayout) {
         if (!featureFlags.isEnabled(Flags.WIDGET_ON_KEYGUARD)) {
             return
         }
@@ -65,4 +68,6 @@
             connect(widgetAreaViewId, END, PARENT_ID, END)
         }
     }
+
+    override fun removeViews(constraintLayout: ConstraintLayout) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index f68078a..82b0324 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.Compile
 import com.android.systemui.util.traceSection
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -239,7 +240,7 @@
 
     private companion object {
         const val TAG = "DisplayRepository"
-        val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+        val DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Compile.IS_DEBUG
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index d8b31a2..ea2568d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -636,7 +636,13 @@
     val CLIPBOARD_SHARED_TRANSITIONS =
             unreleasedFlag("clipboard_shared_transitions", teamfood = true)
 
+    /**
+     * Whether the scene container (Flexiglass) is enabled. Note that [SCENE_CONTAINER] should be
+     * checked and toggled together with [SCENE_CONTAINER_ENABLED] so that ProGuard can remove
+     * unused code from our APK at compile time.
+     */
     // TODO(b/283300105): Tracking Bug
+    @JvmField val SCENE_CONTAINER_ENABLED = false
     @JvmField val SCENE_CONTAINER = unreleasedFlag("scene_container")
 
     // 1900
@@ -674,6 +680,10 @@
     // TODO:(b/283203305): Tracking bug
     @JvmField val TRIM_FONT_CACHES_AT_UNLOCK = unreleasedFlag("trim_font_caches_on_unlock")
 
+    // TODO(b/298380520): Tracking Bug
+    @JvmField
+    val USER_TRACKER_BACKGROUND_CALLBACKS = unreleasedFlag("user_tracker_background_callbacks")
+
     // 2700 - unfold transitions
     // TODO(b/265764985): Tracking Bug
     @Keep
@@ -764,7 +774,7 @@
 
     /** Enable the Compose implementation of the PeopleSpaceActivity. */
     @JvmField
-    val COMPOSE_PEOPLE_SPACE = unreleasedFlag("compose_people_space")
+    val COMPOSE_PEOPLE_SPACE = releasedFlag("compose_people_space")
 
     /** Enable the Compose implementation of the Quick Settings footer actions. */
     @JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt
index 659c5f3..35a9aae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt
@@ -22,17 +22,34 @@
 /** Determines the constraints for the ConstraintSet in the lockscreen root view. */
 interface KeyguardBlueprint {
     val id: String
-    val sections: Array<KeyguardSection>
+    val sections: Set<KeyguardSection>
 
-    fun addViews(constraintLayout: ConstraintLayout) {
-        sections.forEach { it.addViews(constraintLayout) }
+    /**
+     * Add views to new blueprint.
+     *
+     * Finds sections that did not exist in the previous blueprint and add the corresponding views.
+     *
+     * @param previousBluePrint: KeyguardBlueprint the blueprint we are transitioning from.
+     */
+    fun addViews(previousBlueprint: KeyguardBlueprint?, constraintLayout: ConstraintLayout) {
+        sections.subtract((previousBlueprint?.sections ?: setOf()).toSet()).forEach {
+            it.addViews(constraintLayout)
+            it.bindData(constraintLayout)
+        }
+    }
+
+    /**
+     * Remove views of old blueprint.
+     *
+     * Finds sections that are no longer in the next blueprint and remove the corresponding views.
+     *
+     * @param nextBluePrint: KeyguardBlueprint the blueprint we will transition to.
+     */
+    fun removeViews(nextBlueprint: KeyguardBlueprint, constraintLayout: ConstraintLayout) {
+        sections.subtract(nextBlueprint.sections).forEach { it.removeViews(constraintLayout) }
     }
 
     fun applyConstraints(constraintSet: ConstraintSet) {
         sections.forEach { it.applyConstraints(constraintSet) }
     }
-
-    fun onDestroy() {
-        sections.forEach { it.onDestroy() }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt
index 19f50de..48a2146 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt
@@ -23,8 +23,34 @@
  * Lower level modules that determine constraints for a particular section in the lockscreen root
  * view.
  */
-interface KeyguardSection {
-    fun addViews(constraintLayout: ConstraintLayout)
-    fun applyConstraints(constraintSet: ConstraintSet)
-    fun onDestroy() {}
+abstract class KeyguardSection {
+    /** Adds the views to the root view. */
+    abstract fun addViews(constraintLayout: ConstraintLayout)
+    /** Binds the views to data. */
+    abstract fun bindData(constraintLayout: ConstraintLayout)
+    /** Applies layout constraints to the view in respect to the root view. */
+    abstract fun applyConstraints(constraintSet: ConstraintSet)
+    /** Removes views and does any data binding destruction. */
+    abstract fun removeViews(constraintLayout: ConstraintLayout)
+
+    /**
+     * Defines equality as same class.
+     *
+     * This is to enable set operations to be done as an optimization to blueprint transitions.
+     */
+    override fun equals(other: Any?): Boolean {
+        other?.let { other ->
+            return this::class == other::class
+        }
+        return false
+    }
+
+    /**
+     * Defines hashcode as class.
+     *
+     * This is to enable set operations to be done as an optimization to blueprint transitions.
+     */
+    override fun hashCode(): Int {
+        return this::class.hashCode()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index c340e5d..78b72a9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -21,7 +21,6 @@
 import android.util.Log
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
-import androidx.core.view.children
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
@@ -37,26 +36,20 @@
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
                     launch {
                         viewModel.blueprint.collect { blueprint ->
+                            val prevBluePrint = viewModel.currentBluePrint
                             Trace.beginSection("KeyguardBlueprint#applyBlueprint")
                             Log.d(TAG, "applying blueprint: $blueprint")
-                            if (blueprint != viewModel.currentBluePrint) {
-                                viewModel.currentBluePrint?.onDestroy()
+                            // Add and remove views of sections that are not contained by the other.
+                            prevBluePrint?.removeViews(blueprint, constraintLayout)
+                            blueprint.addViews(prevBluePrint, constraintLayout)
+
+                            ConstraintSet().apply {
+                                clone(constraintLayout)
+                                val emptyLayout = ConstraintSet.Layout()
+                                knownIds.forEach { getConstraint(it).layout.copyFrom(emptyLayout) }
+                                blueprint.applyConstraints(this)
+                                applyTo(constraintLayout)
                             }
-                            val constraintSet =
-                                ConstraintSet().apply {
-                                    clone(constraintLayout)
-                                    val emptyLayout = ConstraintSet.Layout()
-                                    knownIds.forEach {
-                                        getConstraint(it).layout.copyFrom(emptyLayout)
-                                    }
-                                    blueprint.addViews(constraintLayout)
-                                    blueprint.applyConstraints(this)
-                                    applyTo(constraintLayout)
-                                }
-                            // Remove all unconstrained views.
-                            constraintLayout.children
-                                .filterNot { constraintSet.knownIds.contains(it.id) }
-                                .forEach { constraintLayout.removeView(it) }
 
                             viewModel.currentBluePrint = blueprint
                             Trace.endSection()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index 5a15fc2..85b2b82 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -55,7 +55,7 @@
     override val id: String = DEFAULT
 
     override val sections =
-        arrayOf(
+        setOf(
             defaultIndicationAreaSection,
             defaultLockIconSection,
             defaultShortcutsSection,
@@ -66,9 +66,12 @@
             splitShadeGuidelines,
         )
 
-    override fun addViews(constraintLayout: ConstraintLayout) {
+    override fun addViews(
+        previousBlueprint: KeyguardBlueprint?,
+        constraintLayout: ConstraintLayout
+    ) {
         if (featureFlags.isEnabled(Flags.LAZY_INFLATE_KEYGUARD)) {
-            super.addViews(constraintLayout)
+            super.addViews(previousBlueprint, constraintLayout)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index 5ef625e..bb3af6c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -46,7 +46,7 @@
     override val id: String = SHORTCUTS_BESIDE_UDFPS
 
     override val sections =
-        arrayOf(
+        setOf(
             defaultIndicationAreaSection,
             defaultLockIconSection,
             defaultAmbientIndicationAreaSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
index 587c6b7..79b7157 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
@@ -18,6 +18,8 @@
 package com.android.systemui.keyguard.ui.view.layout.sections
 
 import android.content.res.Resources
+import android.view.View
+import android.widget.ImageView
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -25,7 +27,9 @@
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.RIGHT
 import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.core.content.res.ResourcesCompat
 import com.android.systemui.R
+import com.android.systemui.animation.view.LaunchableImageView
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -49,12 +53,19 @@
     private val falsingManager: FalsingManager,
     private val indicationController: KeyguardIndicationController,
     private val vibratorHelper: VibratorHelper,
-) : BaseShortcutsSection(), KeyguardSection {
+) : KeyguardSection() {
+    private var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
+    private var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
 
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
             addLeftShortcut(constraintLayout)
             addRightShortcut(constraintLayout)
+        }
+    }
+
+    override fun bindData(constraintLayout: ConstraintLayout) {
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
             leftShortcutHandle =
                 KeyguardQuickAffordanceViewBinder.bind(
                     constraintLayout.requireViewById(R.id.start_button),
@@ -98,4 +109,67 @@
             connect(R.id.end_button, BOTTOM, R.id.lock_icon_view, BOTTOM)
         }
     }
+
+    override fun removeViews(constraintLayout: ConstraintLayout) {
+        leftShortcutHandle?.destroy()
+        rightShortcutHandle?.destroy()
+        constraintLayout.removeView(R.id.start_button)
+        constraintLayout.removeView(R.id.end_button)
+    }
+
+    private fun addLeftShortcut(constraintLayout: ConstraintLayout) {
+        val padding =
+            constraintLayout.resources.getDimensionPixelSize(
+                R.dimen.keyguard_affordance_fixed_padding
+            )
+        val view =
+            LaunchableImageView(constraintLayout.context, null).apply {
+                id = R.id.start_button
+                scaleType = ImageView.ScaleType.FIT_CENTER
+                background =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_bg,
+                        context.theme
+                    )
+                foreground =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_selected_border,
+                        context.theme
+                    )
+                visibility = View.INVISIBLE
+                setPadding(padding, padding, padding, padding)
+            }
+        constraintLayout.addView(view)
+    }
+
+    private fun addRightShortcut(constraintLayout: ConstraintLayout) {
+        if (constraintLayout.findViewById<View>(R.id.end_button) != null) return
+
+        val padding =
+            constraintLayout.resources.getDimensionPixelSize(
+                R.dimen.keyguard_affordance_fixed_padding
+            )
+        val view =
+            LaunchableImageView(constraintLayout.context, null).apply {
+                id = R.id.end_button
+                scaleType = ImageView.ScaleType.FIT_CENTER
+                background =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_bg,
+                        context.theme
+                    )
+                foreground =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_selected_border,
+                        context.theme
+                    )
+                visibility = View.INVISIBLE
+                setPadding(padding, padding, padding, padding)
+            }
+        constraintLayout.addView(view)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutsSection.kt
deleted file mode 100644
index db0cf5a..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutsSection.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package com.android.systemui.keyguard.ui.view.layout.sections
-
-import android.view.View
-import android.widget.ImageView
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
-import androidx.core.content.res.ResourcesCompat
-import com.android.systemui.R
-import com.android.systemui.animation.view.LaunchableImageView
-import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
-
-/** Base class for sections that add lockscreen shortcuts. */
-abstract class BaseShortcutsSection : KeyguardSection {
-    protected open var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
-    protected open var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
-
-    override fun addViews(constraintLayout: ConstraintLayout) {}
-
-    override fun applyConstraints(constraintSet: ConstraintSet) {}
-
-    override fun onDestroy() {
-        leftShortcutHandle?.destroy()
-        rightShortcutHandle?.destroy()
-    }
-
-    protected open fun addLeftShortcut(constraintLayout: ConstraintLayout) {
-        if (constraintLayout.findViewById<View>(R.id.start_button) != null) return
-
-        val padding =
-            constraintLayout.resources.getDimensionPixelSize(
-                R.dimen.keyguard_affordance_fixed_padding
-            )
-        val view =
-            LaunchableImageView(constraintLayout.context, null).apply {
-                id = R.id.start_button
-                scaleType = ImageView.ScaleType.FIT_CENTER
-                background =
-                    ResourcesCompat.getDrawable(
-                        context.resources,
-                        R.drawable.keyguard_bottom_affordance_bg,
-                        context.theme
-                    )
-                foreground =
-                    ResourcesCompat.getDrawable(
-                        context.resources,
-                        R.drawable.keyguard_bottom_affordance_selected_border,
-                        context.theme
-                    )
-                visibility = View.INVISIBLE
-                setPadding(padding, padding, padding, padding)
-            }
-        constraintLayout.addView(view)
-    }
-
-    protected open fun addRightShortcut(constraintLayout: ConstraintLayout) {
-        if (constraintLayout.findViewById<View>(R.id.end_button) != null) return
-
-        val padding =
-            constraintLayout.resources.getDimensionPixelSize(
-                R.dimen.keyguard_affordance_fixed_padding
-            )
-        val view =
-            LaunchableImageView(constraintLayout.context, null).apply {
-                id = R.id.end_button
-                scaleType = ImageView.ScaleType.FIT_CENTER
-                background =
-                    ResourcesCompat.getDrawable(
-                        context.resources,
-                        R.drawable.keyguard_bottom_affordance_bg,
-                        context.theme
-                    )
-                foreground =
-                    ResourcesCompat.getDrawable(
-                        context.resources,
-                        R.drawable.keyguard_bottom_affordance_selected_border,
-                        context.theme
-                    )
-                visibility = View.INVISIBLE
-                setPadding(padding, padding, padding, padding)
-            }
-        constraintLayout.addView(view)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
index f8455c5..ce86e97 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
@@ -18,7 +18,6 @@
 package com.android.systemui.keyguard.ui.view.layout.sections
 
 import android.view.LayoutInflater
-import android.view.View
 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
@@ -46,19 +45,21 @@
     private val featureFlags: FeatureFlags,
     private val keyguardAmbientIndicationViewModel: KeyguardAmbientIndicationViewModel,
     private val keyguardRootViewModel: KeyguardRootViewModel,
-) : KeyguardSection {
+) : KeyguardSection() {
     private var ambientIndicationAreaHandle: KeyguardAmbientIndicationAreaViewBinder.Binding? = null
 
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
-            if (constraintLayout.findViewById<View>(R.id.ambient_indication_container) == null) {
-                val view =
-                    LayoutInflater.from(constraintLayout.context)
-                        .inflate(R.layout.ambient_indication, constraintLayout, false)
+            val view =
+                LayoutInflater.from(constraintLayout.context)
+                    .inflate(R.layout.ambient_indication, constraintLayout, false)
 
-                constraintLayout.addView(view)
-            }
+            constraintLayout.addView(view)
+        }
+    }
 
+    override fun bindData(constraintLayout: ConstraintLayout) {
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
             ambientIndicationAreaHandle =
                 KeyguardAmbientIndicationAreaViewBinder.bind(
                     constraintLayout,
@@ -94,7 +95,9 @@
         }
     }
 
-    override fun onDestroy() {
+    override fun removeViews(constraintLayout: ConstraintLayout) {
         ambientIndicationAreaHandle?.destroy()
+
+        constraintLayout.removeView(R.id.ambient_indication_container)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
index f04bfc6..a45223c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
@@ -18,7 +18,6 @@
 package com.android.systemui.keyguard.ui.view.layout.sections
 
 import android.content.Context
-import android.view.View
 import android.view.ViewGroup
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
@@ -42,17 +41,19 @@
     private val keyguardRootViewModel: KeyguardRootViewModel,
     private val indicationController: KeyguardIndicationController,
     private val featureFlags: FeatureFlags,
-) : KeyguardSection {
+) : KeyguardSection() {
     private val indicationAreaViewId = R.id.keyguard_indication_area
     private var indicationAreaHandle: DisposableHandle? = null
 
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
-            if (constraintLayout.findViewById<View>(indicationAreaViewId) == null) {
-                val view = KeyguardIndicationArea(context, null)
-                constraintLayout.addView(view)
-            }
+            val view = KeyguardIndicationArea(context, null)
+            constraintLayout.addView(view)
+        }
+    }
 
+    override fun bindData(constraintLayout: ConstraintLayout) {
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
             indicationAreaHandle =
                 KeyguardIndicationAreaBinder.bind(
                     constraintLayout,
@@ -90,7 +91,8 @@
         }
     }
 
-    override fun onDestroy() {
+    override fun removeViews(constraintLayout: ConstraintLayout) {
         indicationAreaHandle?.dispose()
+        constraintLayout.removeView(indicationAreaViewId)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
index 3d62f3f..3e91d93 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt
@@ -47,19 +47,23 @@
     private val notificationPanelView: NotificationPanelView,
     private val featureFlags: FeatureFlags,
     private val lockIconViewController: LockIconViewController,
-) : KeyguardSection {
+) : KeyguardSection() {
     private val lockIconViewId = R.id.lock_icon_view
 
     override fun addViews(constraintLayout: ConstraintLayout) {
-        if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
-            notificationPanelView.findViewById<View>(R.id.lock_icon_view).let {
-                notificationPanelView.removeView(it)
-            }
-            if (constraintLayout.findViewById<View>(R.id.lock_icon_view) == null) {
-                val view = LockIconView(context, null).apply { id = R.id.lock_icon_view }
-                constraintLayout.addView(view)
-                lockIconViewController.setLockIconView(view)
-            }
+        if (!featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
+            return
+        }
+        notificationPanelView.findViewById<View>(R.id.lock_icon_view).let {
+            notificationPanelView.removeView(it)
+        }
+        val view = LockIconView(context, null).apply { id = R.id.lock_icon_view }
+        constraintLayout.addView(view)
+    }
+
+    override fun bindData(constraintLayout: ConstraintLayout) {
+        constraintLayout.findViewById<LockIconView?>(R.id.lock_icon_view)?.let {
+            lockIconViewController.setLockIconView(it)
         }
     }
 
@@ -92,6 +96,10 @@
         }
     }
 
+    override fun removeViews(constraintLayout: ConstraintLayout) {
+        constraintLayout.removeView(R.id.lock_icon_view)
+    }
+
     @VisibleForTesting
     internal fun centerLockIcon(center: Point, radius: Float, constraintSet: ConstraintSet) {
         val sensorRect =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index a203e41d..59c5d78 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -40,22 +40,30 @@
     private val sharedNotificationContainer: SharedNotificationContainer,
     private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
     private val controller: NotificationStackScrollLayoutController,
-) : KeyguardSection {
+) : KeyguardSection() {
     override fun addViews(constraintLayout: ConstraintLayout) {
+        if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+            return
+        }
+        // This moves the existing NSSL view to a different parent, as the controller is a
+        // singleton and recreating it has other bad side effects
+        notificationPanelView.findViewById<View?>(R.id.notification_stack_scroller)?.let {
+            (it.parent as ViewGroup).removeView(it)
+            sharedNotificationContainer.addNotificationStackScrollLayout(it)
+        }
+    }
+
+    override fun bindData(constraintLayout: ConstraintLayout) {
         if (featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
-            // This moves the existing NSSL view to a different parent, as the controller is a
-            // singleton and recreating it has other bad side effects
-            notificationPanelView.findViewById<View?>(R.id.notification_stack_scroller)?.let {
-                (it.parent as ViewGroup).removeView(it)
-                sharedNotificationContainer.addNotificationStackScrollLayout(it)
-                SharedNotificationContainerBinder.bind(
-                    sharedNotificationContainer,
-                    sharedNotificationContainerViewModel,
-                    controller,
-                )
-            }
+            SharedNotificationContainerBinder.bind(
+                sharedNotificationContainer,
+                sharedNotificationContainerViewModel,
+                controller,
+            )
         }
     }
 
     override fun applyConstraints(constraintSet: ConstraintSet) {}
+
+    override fun removeViews(constraintLayout: ConstraintLayout) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
index 660cc96..b25f9af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
@@ -49,23 +49,26 @@
     private val keyguardSettingsMenuViewModel: KeyguardSettingsMenuViewModel,
     private val vibratorHelper: VibratorHelper,
     private val activityStarter: ActivityStarter,
-) : KeyguardSection {
+) : KeyguardSection() {
     private var settingsPopupMenuHandle: DisposableHandle? = null
 
     override fun addViews(constraintLayout: ConstraintLayout) {
-        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
-            if (constraintLayout.findViewById<View?>(R.id.keyguard_settings_button) == null) {
-                val view =
-                    LayoutInflater.from(constraintLayout.context)
-                        .inflate(R.layout.keyguard_settings_popup_menu, constraintLayout, false)
-                        .apply {
-                            id = R.id.keyguard_settings_button
-                            isVisible = false
-                            alpha = 0f
-                        } as LaunchableLinearLayout
-                constraintLayout.addView(view)
-            }
+        if (!featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
+            return
+        }
+        val view =
+            LayoutInflater.from(constraintLayout.context)
+                .inflate(R.layout.keyguard_settings_popup_menu, constraintLayout, false)
+                .apply {
+                    id = R.id.keyguard_settings_button
+                    isVisible = false
+                    alpha = 0f
+                } as LaunchableLinearLayout
+        constraintLayout.addView(view)
+    }
 
+    override fun bindData(constraintLayout: ConstraintLayout) {
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
             settingsPopupMenuHandle =
                 KeyguardSettingsViewBinder.bind(
                     constraintLayout.requireViewById<View>(R.id.keyguard_settings_button),
@@ -100,7 +103,8 @@
         }
     }
 
-    override fun onDestroy() {
+    override fun removeViews(constraintLayout: ConstraintLayout) {
         settingsPopupMenuHandle?.dispose()
+        constraintLayout.removeView(R.id.keyguard_settings_button)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index 965910a..c498055 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -18,13 +18,17 @@
 package com.android.systemui.keyguard.ui.view.layout.sections
 
 import android.content.res.Resources
+import android.view.View
+import android.widget.ImageView
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
 import androidx.constraintlayout.widget.ConstraintSet.LEFT
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.RIGHT
+import androidx.core.content.res.ResourcesCompat
 import com.android.systemui.R
+import com.android.systemui.animation.view.LaunchableImageView
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
@@ -48,12 +52,19 @@
     private val falsingManager: FalsingManager,
     private val indicationController: KeyguardIndicationController,
     private val vibratorHelper: VibratorHelper,
-) : BaseShortcutsSection(), KeyguardSection {
+) : KeyguardSection() {
+    private var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
+    private var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
 
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
             addLeftShortcut(constraintLayout)
             addRightShortcut(constraintLayout)
+        }
+    }
+
+    override fun bindData(constraintLayout: ConstraintLayout) {
+        if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
             leftShortcutHandle =
                 KeyguardQuickAffordanceViewBinder.bind(
                     constraintLayout.requireViewById(R.id.start_button),
@@ -98,6 +109,69 @@
         }
     }
 
+    override fun removeViews(constraintLayout: ConstraintLayout) {
+        leftShortcutHandle?.destroy()
+        rightShortcutHandle?.destroy()
+        constraintLayout.removeView(R.id.start_button)
+        constraintLayout.removeView(R.id.end_button)
+    }
+
+    private fun addLeftShortcut(constraintLayout: ConstraintLayout) {
+        val padding =
+            constraintLayout.resources.getDimensionPixelSize(
+                R.dimen.keyguard_affordance_fixed_padding
+            )
+        val view =
+            LaunchableImageView(constraintLayout.context, null).apply {
+                id = R.id.start_button
+                scaleType = ImageView.ScaleType.FIT_CENTER
+                background =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_bg,
+                        context.theme
+                    )
+                foreground =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_selected_border,
+                        context.theme
+                    )
+                visibility = View.INVISIBLE
+                setPadding(padding, padding, padding, padding)
+            }
+        constraintLayout.addView(view)
+    }
+
+    private fun addRightShortcut(constraintLayout: ConstraintLayout) {
+        if (constraintLayout.findViewById<View>(R.id.end_button) != null) return
+
+        val padding =
+            constraintLayout.resources.getDimensionPixelSize(
+                R.dimen.keyguard_affordance_fixed_padding
+            )
+        val view =
+            LaunchableImageView(constraintLayout.context, null).apply {
+                id = R.id.end_button
+                scaleType = ImageView.ScaleType.FIT_CENTER
+                background =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_bg,
+                        context.theme
+                    )
+                foreground =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_selected_border,
+                        context.theme
+                    )
+                visibility = View.INVISIBLE
+                setPadding(padding, padding, padding, padding)
+            }
+        constraintLayout.addView(view)
+    }
+
     /** Method to add shortcuts without applying any data binding. */
     fun addShortcutViews(constraintLayout: ConstraintLayout) {
         addLeftShortcut(constraintLayout)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
index 321d7a7..b144f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
@@ -55,31 +55,34 @@
     private val keyguardViewConfigurator: Lazy<KeyguardViewConfigurator>,
     private val notificationPanelViewController: Lazy<NotificationPanelViewController>,
     private val keyguardMediaController: KeyguardMediaController,
-) : KeyguardSection {
+) : KeyguardSection() {
     private val statusViewId = R.id.keyguard_status_view
 
-    @OptIn(ExperimentalCoroutinesApi::class)
     override fun addViews(constraintLayout: ConstraintLayout) {
+        if (!featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            return
+        }
         // At startup, 2 views with the ID `R.id.keyguard_status_view` will be available.
         // Disable one of them
-        if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
-            notificationPanelView.findViewById<View>(statusViewId)?.let {
-                notificationPanelView.removeView(it)
-            }
-            if (constraintLayout.findViewById<View>(statusViewId) == null) {
-                val keyguardStatusView =
-                    (LayoutInflater.from(context)
-                            .inflate(R.layout.keyguard_status_view, constraintLayout, false)
-                            as KeyguardStatusView)
-                        .apply { clipChildren = false }
+        notificationPanelView.findViewById<View>(statusViewId)?.let {
+            notificationPanelView.removeView(it)
+        }
+        val keyguardStatusView =
+            (LayoutInflater.from(context)
+                    .inflate(R.layout.keyguard_status_view, constraintLayout, false)
+                    as KeyguardStatusView)
+                .apply { clipChildren = false }
+        constraintLayout.addView(keyguardStatusView)
+    }
 
-                val statusViewComponent =
-                    keyguardStatusViewComponentFactory.build(keyguardStatusView)
+    override fun bindData(constraintLayout: ConstraintLayout) {
+        if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            constraintLayout.findViewById<KeyguardStatusView?>(R.id.keyguard_status_view)?.let {
+                val statusViewComponent = keyguardStatusViewComponentFactory.build(it)
                 val controller = statusViewComponent.keyguardStatusViewController
                 controller.init()
-                constraintLayout.addView(keyguardStatusView)
                 keyguardMediaController.attachSplitShadeContainer(
-                    keyguardStatusView.requireViewById<ViewGroup>(R.id.status_view_media_container)
+                    it.requireViewById<ViewGroup>(R.id.status_view_media_container)
                 )
                 keyguardViewConfigurator.get().keyguardStatusViewController = controller
                 notificationPanelViewController.get().updateStatusBarViewController()
@@ -107,7 +110,8 @@
     }
 
     @OptIn(ExperimentalCoroutinesApi::class)
-    override fun onDestroy() {
+    override fun removeViews(constraintLayout: ConstraintLayout) {
+        constraintLayout.removeView(statusViewId)
         keyguardViewConfigurator.get().keyguardStatusViewController = null
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/Extensions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/Extensions.kt
new file mode 100644
index 0000000..94332d2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/Extensions.kt
@@ -0,0 +1,8 @@
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.view.View
+import androidx.constraintlayout.widget.ConstraintLayout
+
+internal fun ConstraintLayout.removeView(viewId: Int) {
+    findViewById<View?>(viewId)?.let { removeView(it) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt
index bd629d5..5e3ea05 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt
@@ -17,7 +17,6 @@
 
 package com.android.systemui.keyguard.ui.view.layout.sections
 
-import android.content.Context
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.VERTICAL
@@ -25,9 +24,11 @@
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import javax.inject.Inject
 
-class SplitShadeGuidelines @Inject constructor(private val context: Context) : KeyguardSection {
+class SplitShadeGuidelines @Inject constructor() : KeyguardSection() {
     override fun addViews(constraintLayout: ConstraintLayout) {}
 
+    override fun bindData(constraintLayout: ConstraintLayout) {}
+
     override fun applyConstraints(constraintSet: ConstraintSet) {
         constraintSet.apply {
             // For use on large screens, it will provide a guideline vertically in the center to
@@ -36,4 +37,6 @@
             setGuidelinePercent(R.id.split_shade_guideline, 0.5f)
         }
     }
+
+    override fun removeViews(constraintLayout: ConstraintLayout) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 8db7abf..c199904 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -163,6 +163,7 @@
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.shade.data.repository.ShadeRepository;
 import com.android.systemui.shade.transition.ShadeTransitionController;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -350,6 +351,7 @@
     private final Interpolator mBounceInterpolator;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final ShadeExpansionStateManager mShadeExpansionStateManager;
+    private final ShadeRepository mShadeRepository;
     private final FalsingTapListener mFalsingTapListener = this::falsingAdditionalTapRequired;
     private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
     private final NotificationGutsManager mGutsManager;
@@ -710,7 +712,8 @@
             VibratorHelper vibratorHelper,
             LatencyTracker latencyTracker,
             PowerManager powerManager,
-            AccessibilityManager accessibilityManager, @DisplayId int displayId,
+            AccessibilityManager accessibilityManager,
+            @DisplayId int displayId,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             MetricsLogger metricsLogger,
             ShadeLogger shadeLogger,
@@ -746,6 +749,7 @@
             ScreenOffAnimationController screenOffAnimationController,
             LockscreenGestureLogger lockscreenGestureLogger,
             ShadeExpansionStateManager shadeExpansionStateManager,
+            ShadeRepository shadeRepository,
             Optional<SysUIUnfoldComponent> unfoldComponent,
             SysUiState sysUiState,
             Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider,
@@ -788,6 +792,7 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mLockscreenGestureLogger = lockscreenGestureLogger;
         mShadeExpansionStateManager = shadeExpansionStateManager;
+        mShadeRepository = shadeRepository;
         mShadeLog = shadeLogger;
         mGutsManager = gutsManager;
         mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
@@ -3952,6 +3957,7 @@
             }
             mExpandedFraction = Math.min(1f,
                     maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
+            mShadeRepository.setLegacyShadeExpansion(mExpandedFraction);
             mQsController.setShadeExpansion(mExpandedHeight, mExpandedFraction);
             mExpansionDragDownAmountPx = h;
             mAmbientState.setExpansionFraction(mExpandedFraction);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 6585fcb..ed719a65 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -77,7 +77,9 @@
             layoutInsetController: NotificationInsetsController,
         ): WindowRootView {
             return if (
-                featureFlags.isEnabled(Flags.SCENE_CONTAINER) && ComposeFacade.isComposeAvailable()
+                Flags.SCENE_CONTAINER_ENABLED &&
+                    featureFlags.isEnabled(Flags.SCENE_CONTAINER) &&
+                    ComposeFacade.isComposeAvailable()
             ) {
                 val sceneWindowRootView =
                     layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 5a8be1e..509921f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -34,21 +34,48 @@
     /** ShadeModel information regarding shade expansion events */
     val shadeModel: Flow<ShadeModel>
 
-    /** Amount qs has expanded. Quick Settings can be expanded without the full shade expansion. */
+    /**
+     * Amount qs has expanded, [0-1]. 0 means fully collapsed, 1 means fully expanded. Quick
+     * Settings can be expanded without the full shade expansion.
+     */
     val qsExpansion: StateFlow<Float>
 
-    /** The amount the shade has expanded */
-    val shadeExpansion: StateFlow<Float>
+    /**
+     * The amount the lockscreen shade has dragged down by the user, [0-1]. 0 means fully collapsed,
+     * 1 means fully expanded.
+     */
+    val lockscreenShadeExpansion: StateFlow<Float>
+
+    /**
+     * NotificationPanelViewController.mExpandedFraction as a StateFlow. This nominally represents
+     * the amount the shade has expanded 0-1 like many other flows in this repo, but there are cases
+     * where its value will be 1 and no shade will be rendered, e.g. whenever the keyguard is
+     * visible and when quick settings is expanded. The confusing nature and impending deletion of
+     * this makes it unsuitable for future development, so usage is discouraged.
+     */
+    @Deprecated("Use ShadeInteractor.shadeExpansion instead")
+    val legacyShadeExpansion: StateFlow<Float>
 
     /** Amount shade has expanded with regard to the UDFPS location */
     val udfpsTransitionToFullShadeProgress: StateFlow<Float>
 
     /** The amount QS has expanded without notifications */
     fun setQsExpansion(qsExpansion: Float)
+
     fun setUdfpsTransitionToFullShadeProgress(progress: Float)
 
-    /** The amount the shade has expanded, [0-1]. 0 means fully collapsed, 1 means fully expanded */
-    fun setShadeExpansion(expansion: Float)
+    /**
+     * Set the amount the shade has dragged down by the user, [0-1]. 0 means fully collapsed, 1
+     * means fully expanded.
+     */
+    fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float)
+
+    /**
+     * Set the legacy expansion value. This should only be called whenever the value of
+     * NotificationPanelViewController.mExpandedFraction changes or in tests.
+     */
+    @Deprecated("Should only be called by NPVC and tests")
+    fun setLegacyShadeExpansion(expandedFraction: Float)
 }
 
 /** Business logic for shade interactions */
@@ -84,18 +111,29 @@
     private val _qsExpansion = MutableStateFlow(0f)
     override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow()
 
-    private val _shadeExpansion = MutableStateFlow(0f)
-    override val shadeExpansion: StateFlow<Float> = _shadeExpansion.asStateFlow()
+    private val _lockscreenShadeExpansion = MutableStateFlow(0f)
+    override val lockscreenShadeExpansion: StateFlow<Float> =
+        _lockscreenShadeExpansion.asStateFlow()
 
     private var _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
     override val udfpsTransitionToFullShadeProgress: StateFlow<Float> =
         _udfpsTransitionToFullShadeProgress.asStateFlow()
+
+    private val _legacyShadeExpansion = MutableStateFlow(0f)
+    @Deprecated("Use ShadeInteractor.shadeExpansion instead")
+    override val legacyShadeExpansion: StateFlow<Float> = _legacyShadeExpansion.asStateFlow()
+
     override fun setQsExpansion(qsExpansion: Float) {
         _qsExpansion.value = qsExpansion
     }
 
-    override fun setShadeExpansion(expansion: Float) {
-        _shadeExpansion.value = expansion
+    @Deprecated("Should only be called by NPVC and tests")
+    override fun setLegacyShadeExpansion(expandedFraction: Float) {
+        _legacyShadeExpansion.value = expandedFraction
+    }
+
+    override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) {
+        _lockscreenShadeExpansion.value = lockscreenShadeExpansion
     }
 
     override fun setUdfpsTransitionToFullShadeProgress(progress: Float) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 288d32e..fd63b89 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.user.domain.interactor.UserInteractor
@@ -31,6 +32,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -45,6 +47,7 @@
     userSetupRepository: UserSetupRepository,
     deviceProvisionedController: DeviceProvisionedController,
     userInteractor: UserInteractor,
+    sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
     repository: ShadeRepository,
 ) {
     /** Emits true if the shade is currently allowed and false otherwise. */
@@ -53,16 +56,31 @@
             .map { it.isShadeEnabled() }
             .stateIn(scope, SharingStarted.Eagerly, initialValue = false)
 
+    /**
+     * Whether split shade, the combined notifications and quick settings shade used for large
+     * screens, is enabled.
+     */
+    val splitShadeEnabled: Flow<Boolean> =
+        sharedNotificationContainerInteractor.configurationBasedDimensions
+            .map { dimens -> dimens.useSplitShade }
+            .distinctUntilChanged()
+
     /** The amount [0-1] that the shade has been opened */
     val shadeExpansion: Flow<Float> =
-        combine(repository.shadeExpansion, keyguardRepository.statusBarState) {
-            shadeExpansion,
-            statusBarState ->
-            // This is required, as shadeExpansion gets reset to 0f even with the shade open
-            if (statusBarState == StatusBarState.SHADE_LOCKED) {
-                1f
-            } else {
-                shadeExpansion
+        combine(
+            repository.lockscreenShadeExpansion,
+            keyguardRepository.statusBarState,
+            repository.legacyShadeExpansion,
+            repository.qsExpansion,
+            splitShadeEnabled
+        ) { dragDownAmount, statusBarState, legacyShadeExpansion, qsExpansion, splitShadeEnabled ->
+            when (statusBarState) {
+                // legacyShadeExpansion is 1 instead of 0 when QS is expanded
+                StatusBarState.SHADE ->
+                    if (!splitShadeEnabled && qsExpansion > 0f) 0f else legacyShadeExpansion
+                StatusBarState.KEYGUARD -> dragDownAmount
+                // This is required, as shadeExpansion gets reset to 0f even with the shade open
+                StatusBarState.SHADE_LOCKED -> 1f
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index f004982..73bbbca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -447,7 +447,7 @@
                 if (!nsslController.isInLockedDownShade() || field == 0f || forceApplyAmount) {
                     fractionToShade =
                         MathUtils.saturate(dragDownAmount / notificationShelfTransitionDistance)
-                    shadeRepository.setShadeExpansion(fractionToShade)
+                    shadeRepository.setLockscreenShadeExpansion(fractionToShade)
                     nsslController.setTransitionToFullShadeAmount(fractionToShade)
 
                     qsTransitionController.dragDownAmount = value
@@ -857,12 +857,12 @@
             MotionEvent.ACTION_MOVE -> {
                 val h = y - initialTouchY
                 // Adjust the touch slop if another gesture may be being performed.
-                val touchSlop = if (event.classification
-                    == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE) {
-                    touchSlop * slopMultiplier
-                } else {
-                    touchSlop
-                }
+                val touchSlop =
+                    if (event.classification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE) {
+                        touchSlop * slopMultiplier
+                    } else {
+                        touchSlop
+                    }
                 if (h > touchSlop && h > Math.abs(x - initialTouchX)) {
                     isDraggingDown = true
                     captureStartingChild(initialTouchX, initialTouchY)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
index be9f6ca..09cb929 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/communal/ui/view/layout/blueprints/DefaultCommunalBlueprintTest.kt
@@ -33,7 +33,7 @@
     @Test
     fun addView() {
         val constraintLayout = ConstraintLayout(context, null)
-        blueprint.addViews(constraintLayout)
+        blueprint.addViews(null, constraintLayout)
         verify(widgetSection).addViews(constraintLayout)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 3b4eab2..bf57ecb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
@@ -34,10 +35,12 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
+import com.android.systemui.util.mockito.whenever
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -81,7 +84,7 @@
     @Test
     fun addViews() {
         val constraintLayout = ConstraintLayout(context, null)
-        underTest.addViews(constraintLayout)
+        underTest.addViews(null, constraintLayout)
         underTest.sections.forEach { verify(it, never()).addViews(constraintLayout) }
     }
 
@@ -89,11 +92,35 @@
     fun addViews_lazyInflateFlagOn() {
         featureFlags.set(Flags.LAZY_INFLATE_KEYGUARD, true)
         val constraintLayout = ConstraintLayout(context, null)
-        underTest.addViews(constraintLayout)
+        underTest.addViews(null, constraintLayout)
         underTest.sections.forEach { verify(it).addViews(constraintLayout) }
     }
 
     @Test
+    fun addViews_withPrevBlueprint() {
+        val prevBlueprint = mock(KeyguardBlueprint::class.java)
+        whenever(prevBlueprint.sections)
+            .thenReturn(underTest.sections.minus(defaultLockIconSection))
+        featureFlags.set(Flags.LAZY_INFLATE_KEYGUARD, true)
+        val constraintLayout = ConstraintLayout(context, null)
+        underTest.addViews(prevBlueprint, constraintLayout)
+        underTest.sections.minus(defaultLockIconSection).forEach {
+            verify(it, never()).addViews(constraintLayout)
+        }
+
+        verify(defaultLockIconSection).addViews(constraintLayout)
+    }
+
+    @Test
+    fun addViews_withNextBlueprint() {
+        val nextBlueprint = mock(KeyguardBlueprint::class.java)
+        whenever(nextBlueprint.sections).thenReturn(setOf())
+        val constraintLayout = ConstraintLayout(context, null)
+        underTest.removeViews(nextBlueprint, constraintLayout)
+        underTest.sections.forEach { verify(it).removeViews(constraintLayout) }
+    }
+
+    @Test
     fun applyConstraints() {
         val cs = ConstraintSet()
         underTest.applyConstraints(cs)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
index 798b23e..4e31af22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
@@ -18,14 +18,17 @@
 package com.android.systemui.keyguard.ui.view.layout.sections
 
 import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.statusbar.KeyguardIndicationController
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -58,7 +61,23 @@
     }
 
     @Test
-    fun apply() {
+    fun addViewsConditionally() {
+        whenever(featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)).thenReturn(true)
+        val constraintLayout = ConstraintLayout(context, null)
+        underTest.addViews(constraintLayout)
+        assertThat(constraintLayout.childCount).isGreaterThan(0)
+    }
+
+    @Test
+    fun addViewsConditionally_migrateFlagOff() {
+        whenever(featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)).thenReturn(false)
+        val constraintLayout = ConstraintLayout(context, null)
+        underTest.addViews(constraintLayout)
+        assertThat(constraintLayout.childCount).isEqualTo(0)
+    }
+
+    @Test
+    fun applyConstraints() {
         val cs = ConstraintSet()
         underTest.applyConstraints(cs)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt
index 1192a80..1c3b5e61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt
@@ -19,6 +19,7 @@
 
 import android.graphics.Point
 import android.view.WindowManager
+import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -27,7 +28,9 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.shade.NotificationPanelView
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -64,7 +67,23 @@
     }
 
     @Test
-    fun apply() {
+    fun addViewsConditionally() {
+        whenever(featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)).thenReturn(true)
+        val constraintLayout = ConstraintLayout(context, null)
+        underTest.addViews(constraintLayout)
+        assertThat(constraintLayout.childCount).isGreaterThan(0)
+    }
+
+    @Test
+    fun addViewsConditionally_migrateFlagOff() {
+        whenever(featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)).thenReturn(false)
+        val constraintLayout = ConstraintLayout(context, null)
+        underTest.addViews(constraintLayout)
+        assertThat(constraintLayout.childCount).isEqualTo(0)
+    }
+
+    @Test
+    fun applyConstraints() {
         val cs = ConstraintSet()
         underTest.applyConstraints(cs)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index c573ac63..1c9ec27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -626,6 +626,7 @@
                 mScreenOffAnimationController,
                 mLockscreenGestureLogger,
                 mShadeExpansionStateManager,
+                mShadeRepository,
                 mSysUIUnfoldComponent,
                 mSysUiState,
                 () -> mKeyguardBottomAreaViewController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index ab0ae05..e42a7a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -37,6 +37,7 @@
 import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.fragments.FragmentHostManager;
@@ -61,6 +62,7 @@
 import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
 import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
@@ -174,6 +176,9 @@
                         new FakeUserSetupRepository(),
                         mDeviceProvisionedController,
                         mUserInteractor,
+                        new SharedNotificationContainerInteractor(
+                                new FakeConfigurationRepository(),
+                                mContext),
                         mShadeRepository
                 );
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index e6e7482..41ea5b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -25,7 +25,9 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -36,6 +38,7 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
 import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
@@ -52,6 +55,7 @@
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -71,6 +75,12 @@
     private val disableFlagsRepository = FakeDisableFlagsRepository()
     private val keyguardRepository = FakeKeyguardRepository()
     private val shadeRepository = FakeShadeRepository()
+    private val configurationRepository = FakeConfigurationRepository()
+    private val sharedNotificationContainerInteractor =
+        SharedNotificationContainerInteractor(
+            configurationRepository,
+            mContext,
+        )
 
     @Mock private lateinit var manager: UserManager
     @Mock private lateinit var headlessSystemUserMode: HeadlessSystemUserMode
@@ -145,6 +155,7 @@
                 userSetupRepository,
                 deviceProvisionedController,
                 userInteractor,
+                sharedNotificationContainerInteractor,
                 shadeRepository,
             )
     }
@@ -363,7 +374,7 @@
             val actual by collectLastValue(underTest.shadeExpansion)
 
             keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
-            shadeRepository.setShadeExpansion(0.5f)
+            shadeRepository.setLockscreenShadeExpansion(0.5f)
 
             assertThat(actual).isEqualTo(1f)
         }
@@ -375,10 +386,52 @@
 
             keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
 
-            shadeRepository.setShadeExpansion(0.5f)
+            shadeRepository.setLockscreenShadeExpansion(0.5f)
             assertThat(actual).isEqualTo(0.5f)
 
-            shadeRepository.setShadeExpansion(0.8f)
+            shadeRepository.setLockscreenShadeExpansion(0.8f)
             assertThat(actual).isEqualTo(0.8f)
         }
+
+    fun shadeExpansionWhenInSplitShadeAndQsExpanded() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.shadeExpansion)
+
+            // WHEN split shade is enabled and QS is expanded
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            configurationRepository.onAnyConfigurationChange()
+            runCurrent()
+            shadeRepository.setQsExpansion(.5f)
+            shadeRepository.setLegacyShadeExpansion(.7f)
+
+            // THEN legacy shade expansion is passed through
+            assertThat(actual).isEqualTo(.7f)
+        }
+
+    fun shadeExpansionWhenNotInSplitShadeAndQsExpanded() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.shadeExpansion)
+
+            // WHEN split shade is not enabled and QS is expanded
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            shadeRepository.setQsExpansion(.5f)
+            shadeRepository.setLegacyShadeExpansion(1f)
+
+            // THEN shade expansion is zero
+            assertThat(actual).isEqualTo(0f)
+        }
+
+    fun shadeExpansionWhenNotInSplitShadeAndQsCollapsed() =
+        testScope.runTest {
+            val actual by collectLastValue(underTest.shadeExpansion)
+
+            // WHEN split shade is not enabled and QS is expanded
+            keyguardRepository.setStatusBarState(StatusBarState.SHADE)
+            shadeRepository.setQsExpansion(0f)
+            shadeRepository.setLegacyShadeExpansion(.6f)
+
+            // THEN shade expansion is zero
+            assertThat(actual).isEqualTo(.6f)
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
index fdaea22..e086712 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
@@ -121,18 +121,33 @@
         }
 
     @Test
-    fun updateShadeExpansion() =
+    fun updateDragDownAmount() =
         testScope.runTest {
-            assertThat(underTest.shadeExpansion.value).isEqualTo(0f)
+            assertThat(underTest.lockscreenShadeExpansion.value).isEqualTo(0f)
 
-            underTest.setShadeExpansion(.5f)
-            assertThat(underTest.shadeExpansion.value).isEqualTo(.5f)
+            underTest.setLockscreenShadeExpansion(.5f)
+            assertThat(underTest.lockscreenShadeExpansion.value).isEqualTo(.5f)
 
-            underTest.setShadeExpansion(.82f)
-            assertThat(underTest.shadeExpansion.value).isEqualTo(.82f)
+            underTest.setLockscreenShadeExpansion(.82f)
+            assertThat(underTest.lockscreenShadeExpansion.value).isEqualTo(.82f)
 
-            underTest.setShadeExpansion(1f)
-            assertThat(underTest.shadeExpansion.value).isEqualTo(1f)
+            underTest.setLockscreenShadeExpansion(1f)
+            assertThat(underTest.lockscreenShadeExpansion.value).isEqualTo(1f)
+        }
+
+    @Test
+    fun updateLegacyShadeExpansion() =
+        testScope.runTest {
+            assertThat(underTest.legacyShadeExpansion.value).isEqualTo(0f)
+
+            underTest.setLegacyShadeExpansion(.5f)
+            assertThat(underTest.legacyShadeExpansion.value).isEqualTo(.5f)
+
+            underTest.setLegacyShadeExpansion(.82f)
+            assertThat(underTest.legacyShadeExpansion.value).isEqualTo(.82f)
+
+            underTest.setLegacyShadeExpansion(1f)
+            assertThat(underTest.legacyShadeExpansion.value).isEqualTo(1f)
         }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 51e72c6..6f75880 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -10,6 +10,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -29,6 +30,7 @@
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import com.android.systemui.statusbar.phone.CentralSurfaces
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.phone.LSShadeTransitionLogger
@@ -102,6 +104,11 @@
     @Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback
     private val disableFlagsRepository = FakeDisableFlagsRepository()
     private val keyguardRepository = FakeKeyguardRepository()
+    private val configurationRepository = FakeConfigurationRepository()
+    private val sharedNotificationContainerInteractor = SharedNotificationContainerInteractor(
+        configurationRepository,
+        mContext,
+    )
     private val shadeInteractor =
         ShadeInteractor(
             testScope.backgroundScope,
@@ -110,6 +117,7 @@
             userSetupRepository = FakeUserSetupRepository(),
             deviceProvisionedController = mock(),
             userInteractor = mock(),
+            sharedNotificationContainerInteractor,
             repository = FakeShadeRepository(),
         )
     private val powerInteractor =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 55b52dc..75fb22d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.shade.data.repository.FakeShadeRepository
@@ -97,7 +98,11 @@
                 keyguardTransitionInteractor = it.keyguardTransitionInteractor
                 keyguardTransitionRepository = it.repository
             }
-
+        sharedNotificationContainerInteractor =
+            SharedNotificationContainerInteractor(
+                configurationRepository,
+                mContext,
+            )
         shadeInteractor =
             ShadeInteractor(
                 testScope.backgroundScope,
@@ -106,14 +111,9 @@
                 userSetupRepository,
                 deviceProvisionedController,
                 userInteractor,
+                sharedNotificationContainerInteractor,
                 shadeRepository,
             )
-
-        sharedNotificationContainerInteractor =
-            SharedNotificationContainerInteractor(
-                configurationRepository,
-                mContext,
-            )
         underTest =
             SharedNotificationContainerViewModel(
                 sharedNotificationContainerInteractor,
@@ -228,7 +228,7 @@
             val isOnLockscreenWithoutShade by collectLastValue(underTest.isOnLockscreenWithoutShade)
 
             // First on AOD
-            shadeRepository.setShadeExpansion(0f)
+            shadeRepository.setLockscreenShadeExpansion(0f)
             shadeRepository.setQsExpansion(0f)
             keyguardTransitionRepository.sendTransitionStep(
                 TransitionStep(
@@ -242,19 +242,19 @@
             showLockscreen()
 
             // While state is LOCKSCREEN, validate variations of both shade and qs expansion
-            shadeRepository.setShadeExpansion(0.1f)
+            shadeRepository.setLockscreenShadeExpansion(0.1f)
             shadeRepository.setQsExpansion(0f)
             assertThat(isOnLockscreenWithoutShade).isFalse()
 
-            shadeRepository.setShadeExpansion(0.1f)
+            shadeRepository.setLockscreenShadeExpansion(0.1f)
             shadeRepository.setQsExpansion(0.1f)
             assertThat(isOnLockscreenWithoutShade).isFalse()
 
-            shadeRepository.setShadeExpansion(0f)
+            shadeRepository.setLockscreenShadeExpansion(0f)
             shadeRepository.setQsExpansion(0.1f)
             assertThat(isOnLockscreenWithoutShade).isFalse()
 
-            shadeRepository.setShadeExpansion(0f)
+            shadeRepository.setLockscreenShadeExpansion(0f)
             shadeRepository.setQsExpansion(0f)
             assertThat(isOnLockscreenWithoutShade).isTrue()
         }
@@ -366,8 +366,9 @@
         }
 
     private suspend fun showLockscreen() {
-        shadeRepository.setShadeExpansion(0f)
+        shadeRepository.setLockscreenShadeExpansion(0f)
         shadeRepository.setQsExpansion(0f)
+        keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
         keyguardTransitionRepository.sendTransitionStep(
             TransitionStep(
                 to = KeyguardState.LOCKSCREEN,
@@ -377,8 +378,9 @@
     }
 
     private suspend fun showLockscreenWithShadeExpanded() {
-        shadeRepository.setShadeExpansion(1f)
+        shadeRepository.setLockscreenShadeExpansion(1f)
         shadeRepository.setQsExpansion(0f)
+        keyguardRepository.setStatusBarState(StatusBarState.SHADE_LOCKED)
         keyguardTransitionRepository.sendTransitionStep(
             TransitionStep(
                 to = KeyguardState.LOCKSCREEN,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index ccddca2..08152a3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -33,8 +33,12 @@
     private val _udfpsTransitionToFullShadeProgress = MutableStateFlow(0f)
     override val udfpsTransitionToFullShadeProgress = _udfpsTransitionToFullShadeProgress
 
-    private val _shadeExpansion = MutableStateFlow(0f)
-    override val shadeExpansion = _shadeExpansion
+    private val _lockscreenShadeExpansion = MutableStateFlow(0f)
+    override val lockscreenShadeExpansion = _lockscreenShadeExpansion
+
+    private val _legacyShadeExpansion = MutableStateFlow(0f)
+    @Deprecated("Use ShadeInteractor instead")
+    override val legacyShadeExpansion = _legacyShadeExpansion
 
     fun setShadeModel(model: ShadeModel) {
         _shadeModel.value = model
@@ -48,7 +52,12 @@
         _udfpsTransitionToFullShadeProgress.value = progress
     }
 
-    override fun setShadeExpansion(expansion: Float) {
-        _shadeExpansion.value = expansion
+    override fun setLockscreenShadeExpansion(lockscreenShadeExpansion: Float) {
+        _lockscreenShadeExpansion.value = lockscreenShadeExpansion
+    }
+
+    @Deprecated("Should only be called by NPVC and tests")
+    override fun setLegacyShadeExpansion(expandedFraction: Float) {
+        _legacyShadeExpansion.value = expandedFraction
     }
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index cf6092e..9e7b897 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -136,7 +136,8 @@
     @Nullable private final SecureWindowCallback mSecureWindowCallback;
     @Nullable private final Set<String> mDisplayCategories;
 
-    private final boolean mShowTasksInHostDeviceRecents;
+    @GuardedBy("mGenericWindowPolicyControllerLock")
+    private boolean mShowTasksInHostDeviceRecents;
 
     /**
      * Creates a window policy controller that is generic to the different use cases of virtual
@@ -204,6 +205,15 @@
         mDisplayId = displayId;
     }
 
+    /**
+     * Set whether to show activities in recents on the host device.
+     */
+    public void setShowInHostDeviceRecents(boolean showInHostDeviceRecents) {
+        synchronized (mGenericWindowPolicyControllerLock) {
+            mShowTasksInHostDeviceRecents = showInHostDeviceRecents;
+        }
+    }
+
     /** Register a listener for running applications changes. */
     public void registerRunningAppsChangedListener(@NonNull RunningAppsChangedListener listener) {
         synchronized (mGenericWindowPolicyControllerLock) {
@@ -347,7 +357,9 @@
 
     @Override
     public boolean canShowTasksInHostDeviceRecents() {
-        return mShowTasksInHostDeviceRecents;
+        synchronized (mGenericWindowPolicyControllerLock) {
+            return mShowTasksInHostDeviceRecents;
+        }
     }
 
     @Override
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index b8b42fba..56afeb1 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -19,6 +19,8 @@
 import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_ENABLED;
 import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
 import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
@@ -42,6 +44,7 @@
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.flags.Flags;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtual.sensor.VirtualSensorEvent;
 import android.content.AttributionSource;
@@ -82,6 +85,7 @@
 import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseIntArray;
 import android.view.Display;
 import android.view.WindowManager;
 import android.widget.Toast;
@@ -130,8 +134,8 @@
     private final int mOwnerUid;
     private final VirtualDeviceLog mVirtualDeviceLog;
     private final String mOwnerPackageName;
-    private int mDeviceId;
-    private @Nullable String mPersistentDeviceId;
+    private final int mDeviceId;
+    private @Nullable final String mPersistentDeviceId;
     // Thou shall not hold the mVirtualDeviceLock over the mInputController calls.
     // Holding the lock can lead to lock inversion with GlobalWindowManagerLock.
     // 1. After display is created the window manager calls into VDM during construction
@@ -147,6 +151,8 @@
     private final IBinder mAppToken;
     private final VirtualDeviceParams mParams;
     @GuardedBy("mVirtualDeviceLock")
+    private final SparseIntArray mDevicePolicies;
+    @GuardedBy("mVirtualDeviceLock")
     private final SparseArray<VirtualDisplayWrapper> mVirtualDisplays = new SparseArray<>();
     private final IVirtualDeviceActivityListener mActivityListener;
     private final IVirtualDeviceSoundEffectListener mSoundEffectListener;
@@ -154,7 +160,7 @@
     @GuardedBy("mVirtualDeviceLock")
     private final Map<IBinder, IntentFilter> mIntentInterceptors = new ArrayMap<>();
     @NonNull
-    private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
+    private final Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
     // The default setting for showing the pointer on new displays.
     @GuardedBy("mVirtualDeviceLock")
     private boolean mDefaultShowPointerIcon = true;
@@ -261,6 +267,7 @@
         mDeviceId = deviceId;
         mAppToken = token;
         mParams = params;
+        mDevicePolicies = params.getDevicePolicies();
         mDisplayManager = displayManager;
         if (inputController == null) {
             mInputController = new InputController(
@@ -323,7 +330,13 @@
     /** Returns the policy specified for this policy type */
     public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
             @VirtualDeviceParams.PolicyType int policyType) {
-        return mParams.getDevicePolicy(policyType);
+        if (Flags.dynamicPolicy()) {
+            synchronized (mVirtualDeviceLock) {
+                return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
+            }
+        } else {
+            return mParams.getDevicePolicy(policyType);
+        }
     }
 
     /** Returns device-specific audio session id for playback. */
@@ -412,15 +425,13 @@
     public void close() {
         super.close_enforcePermission();
         // Remove about-to-be-closed virtual device from the service before butchering it.
-        boolean removed = mService.removeVirtualDevice(mDeviceId);
-        mVirtualDeviceLog.logClosed(mDeviceId, mOwnerUid);
-        mDeviceId = Context.DEVICE_ID_INVALID;
-
-        // Device is already closed.
-        if (!removed) {
+        if (!mService.removeVirtualDevice(mDeviceId)) {
+            // Device is already closed.
             return;
         }
 
+        mVirtualDeviceLog.logClosed(mDeviceId, mOwnerUid);
+
         final long ident = Binder.clearCallingIdentity();
         try {
             VirtualDisplayWrapper[] virtualDisplaysToBeReleased;
@@ -510,6 +521,27 @@
 
     @Override // Binder call
     @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
+            @VirtualDeviceParams.DevicePolicy int devicePolicy) {
+        super.setDevicePolicy_enforcePermission();
+        switch (policyType) {
+            case POLICY_TYPE_RECENTS:
+                synchronized (mVirtualDeviceLock) {
+                    mDevicePolicies.put(policyType, devicePolicy);
+                    for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                        mVirtualDisplays.valueAt(i).getWindowPolicyController()
+                                .setShowInHostDeviceRecents(devicePolicy == DEVICE_POLICY_DEFAULT);
+                    }
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("Device policy " + policyType
+                        + " cannot be changed at runtime. ");
+        }
+    }
+
+    @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
         super.createVirtualDpad_enforcePermission();
         Objects.requireNonNull(config);
@@ -787,6 +819,7 @@
         mParams.dump(fout, "        ");
         fout.println("    mVirtualDisplayIds: ");
         synchronized (mVirtualDeviceLock) {
+            fout.println("    mDevicePolicies: " + mDevicePolicies);
             for (int i = 0; i < mVirtualDisplays.size(); i++) {
                 fout.println("      " + mVirtualDisplays.keyAt(i));
             }
@@ -813,9 +846,7 @@
                         this::onSecureWindowShown,
                         this::shouldInterceptIntent,
                         displayCategories,
-                        mParams.getDevicePolicy(
-                                VirtualDeviceParams.POLICY_TYPE_RECENTS)
-                                == VirtualDeviceParams.DEVICE_POLICY_DEFAULT);
+                        mParams.getDevicePolicy(POLICY_TYPE_RECENTS) == DEVICE_POLICY_DEFAULT);
         gwpc.registerRunningAppsChangedListener(/* listener= */ this);
         return gwpc;
     }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b994105..f3ad4b4 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -472,6 +472,8 @@
     private SensorManager mSensorManager;
     private BrightnessTracker mBrightnessTracker;
 
+    private SmallAreaDetectionController mSmallAreaDetectionController;
+
 
     // Whether minimal post processing is allowed by the user.
     @GuardedBy("mSyncRoot")
@@ -738,6 +740,8 @@
         filter.addAction(Intent.ACTION_DOCK_EVENT);
 
         mContext.registerReceiver(mIdleModeReceiver, filter);
+
+        mSmallAreaDetectionController = SmallAreaDetectionController.create(mContext);
     }
 
     @VisibleForTesting
@@ -3128,6 +3132,9 @@
         pw.println();
         mDisplayModeDirector.dump(pw);
         mBrightnessSynchronizer.dump(pw);
+        if (mSmallAreaDetectionController != null) {
+            mSmallAreaDetectionController.dump(pw);
+        }
     }
 
     private static float[] getFloatArray(TypedArray array) {
diff --git a/services/core/java/com/android/server/display/SmallAreaDetectionController.java b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
new file mode 100644
index 0000000..adaa539
--- /dev/null
+++ b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Map;
+
+final class SmallAreaDetectionController {
+    private static native void nativeUpdateSmallAreaDetection(int[] uids, float[] thresholds);
+    private static native void nativeSetSmallAreaDetectionThreshold(int uid, float threshold);
+
+    // TODO(b/281720315): Move this to DeviceConfig once server side ready.
+    private static final String KEY_SMALL_AREA_DETECTION_ALLOWLIST =
+            "small_area_detection_allowlist";
+
+    private final Object mLock = new Object();
+    private final Context mContext;
+    private final PackageManagerInternal mPackageManager;
+    private final UserManagerInternal mUserManager;
+    @GuardedBy("mLock")
+    private final Map<String, Float> mAllowPkgMap = new ArrayMap<>();
+    // TODO(b/298722189): Update allowlist when user changes
+    @GuardedBy("mLock")
+    private int[] mUserIds;
+
+    static SmallAreaDetectionController create(@NonNull Context context) {
+        final SmallAreaDetectionController controller =
+                new SmallAreaDetectionController(context, DeviceConfigInterface.REAL);
+        final String property = DeviceConfigInterface.REAL.getProperty(
+                DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_SMALL_AREA_DETECTION_ALLOWLIST);
+        controller.updateAllowlist(property);
+        return controller;
+    }
+
+    @VisibleForTesting
+    SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig) {
+        mContext = context;
+        mPackageManager = LocalServices.getService(PackageManagerInternal.class);
+        mUserManager = LocalServices.getService(UserManagerInternal.class);
+        deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+                BackgroundThread.getExecutor(),
+                new SmallAreaDetectionController.OnPropertiesChangedListener());
+        mPackageManager.getPackageList(new PackageReceiver());
+    }
+
+    @VisibleForTesting
+    void updateAllowlist(@Nullable String property) {
+        synchronized (mLock) {
+            mAllowPkgMap.clear();
+            if (property != null) {
+                final String[] mapStrings = property.split(",");
+                for (String mapString : mapStrings) putToAllowlist(mapString);
+            } else {
+                final String[] defaultMapStrings = mContext.getResources()
+                        .getStringArray(R.array.config_smallAreaDetectionAllowlist);
+                for (String defaultMapString : defaultMapStrings) putToAllowlist(defaultMapString);
+            }
+            updateSmallAreaDetection();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void putToAllowlist(String rowData) {
+        // Data format: package:threshold - e.g. "com.abc.music:0.05"
+        final String[] items = rowData.split(":");
+        if (items.length == 2) {
+            try {
+                final String pkg = items[0];
+                final float threshold = Float.valueOf(items[1]);
+                mAllowPkgMap.put(pkg, threshold);
+            } catch (Exception e) {
+                // Just skip if items[1] - the threshold is not parsable number
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void updateUidListForAllUsers(SparseArray<Float> list, String pkg, float threshold) {
+        for (int i = 0; i < mUserIds.length; i++) {
+            final int userId = mUserIds[i];
+            final int uid = mPackageManager.getPackageUid(pkg, 0, userId);
+            if (uid > 0) list.put(uid, threshold);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void updateSmallAreaDetection() {
+        if (mAllowPkgMap.isEmpty()) return;
+
+        mUserIds = mUserManager.getUserIds();
+
+        final SparseArray<Float> uidThresholdList = new SparseArray<>();
+        for (String pkg : mAllowPkgMap.keySet()) {
+            final float threshold = mAllowPkgMap.get(pkg);
+            updateUidListForAllUsers(uidThresholdList, pkg, threshold);
+        }
+
+        final int[] uids = new int[uidThresholdList.size()];
+        final float[] thresholds = new float[uidThresholdList.size()];
+        for (int i = 0; i < uidThresholdList.size();  i++) {
+            uids[i] = uidThresholdList.keyAt(i);
+            thresholds[i] = uidThresholdList.valueAt(i);
+        }
+        updateSmallAreaDetection(uids, thresholds);
+    }
+
+    @VisibleForTesting
+    void updateSmallAreaDetection(int[] uids, float[] thresholds) {
+        nativeUpdateSmallAreaDetection(uids, thresholds);
+    }
+
+    void setSmallAreaDetectionThreshold(int uid, float threshold) {
+        nativeSetSmallAreaDetectionThreshold(uid, threshold);
+    }
+
+    void dump(PrintWriter pw) {
+        pw.println("Small area detection allowlist");
+        pw.println("  Packages:");
+        synchronized (mLock) {
+            for (String pkg : mAllowPkgMap.keySet()) {
+                pw.println("    " + pkg + " threshold = " + mAllowPkgMap.get(pkg));
+            }
+            pw.println("  mUserIds=" + Arrays.toString(mUserIds));
+        }
+    }
+
+    private class OnPropertiesChangedListener implements DeviceConfig.OnPropertiesChangedListener {
+        public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+            if (properties.getKeyset().contains(KEY_SMALL_AREA_DETECTION_ALLOWLIST)) {
+                updateAllowlist(
+                        properties.getString(KEY_SMALL_AREA_DETECTION_ALLOWLIST, null /*default*/));
+            }
+        }
+    }
+
+    private final class PackageReceiver implements PackageManagerInternal.PackageListObserver {
+        @Override
+        public void onPackageAdded(@NonNull String packageName, int uid) {
+            synchronized (mLock) {
+                if (mAllowPkgMap.containsKey(packageName)) {
+                    setSmallAreaDetectionThreshold(uid, mAllowPkgMap.get(packageName));
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 1908e4d..dcac8c9 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -13,3 +13,10 @@
   description: "This flag controls the polite notification feature"
   bug: "270456865"
 }
+
+flag {
+  name: "refactor_attention_helper"
+  namespace: "systemui"
+  description: "This flag controls the refactoring of NMS to NotificationAttentionHelper"
+  bug: "291907312"
+}
diff --git a/services/core/java/com/android/server/pm/PackageArchiverService.java b/services/core/java/com/android/server/pm/PackageArchiverService.java
index 9c31dc9..29f49c8 100644
--- a/services/core/java/com/android/server/pm/PackageArchiverService.java
+++ b/services/core/java/com/android/server/pm/PackageArchiverService.java
@@ -17,17 +17,26 @@
 package com.android.server.pm;
 
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE;
+import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.content.Context;
+import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.IPackageArchiverService;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
+import android.content.pm.PackageArchiver;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.ParcelableException;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -36,6 +45,7 @@
 import com.android.server.pm.pkg.ArchiveState;
 import com.android.server.pm.pkg.ArchiveState.ArchiveActivityInfo;
 import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateInternal;
 
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -51,7 +61,12 @@
  */
 public class PackageArchiverService extends IPackageArchiverService.Stub {
 
-    private static final String TAG = "PackageArchiver";
+    /**
+     * The maximum time granted for an app store to start a foreground service when unarchival
+     * is requested.
+     */
+    // TODO(b/297358628) Make this configurable through a flag.
+    private static final int DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS = 120 * 1000;
 
     private final Context mContext;
     private final PackageManagerService mPm;
@@ -82,17 +97,14 @@
         snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
                 "archiveApp");
         verifyCaller(providedUid, binderUid);
-        PackageStateInternal ps = getPackageState(packageName, snapshot, binderUid, userId);
-        verifyInstaller(packageName, ps);
-
-        // TODO(b/291569242) Verify that this list is not empty and return failure with
-        //  intentsender
-        List<LauncherActivityInfo> mainActivities = getLauncherApps().getActivityList(
-                ps.getPackageName(),
-                new UserHandle(userId));
-
-        // TODO(b/282952870) Bug: should happen after the uninstall completes successfully
-        storeArchiveState(ps, mainActivities, userId);
+        ArchiveState archiveState;
+        try {
+            archiveState = createArchiveState(packageName, userId);
+            // TODO(b/282952870) Should be reverted if uninstall fails/cancels
+            storeArchiveState(packageName, archiveState, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new ParcelableException(e);
+        }
 
         // TODO(b/278553670) Add special strings for the delete dialog
         mPm.mInstallerService.uninstall(
@@ -100,25 +112,154 @@
                 callerPackageName, DELETE_KEEP_DATA, intentSender, userId);
     }
 
-    private static void verifyInstaller(String packageName, PackageStateInternal ps) {
-        if (ps.getInstallSource().mUpdateOwnerPackageName == null
-                && ps.getInstallSource().mInstallerPackageName == null) {
+    private ArchiveState createArchiveState(String packageName, int userId)
+            throws PackageManager.NameNotFoundException {
+        PackageStateInternal ps = getPackageState(packageName, mPm.snapshotComputer(),
+                Binder.getCallingUid(), userId);
+        String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
+        if (responsibleInstallerPackage == null) {
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("No installer found to archive app %s.",
+                            packageName));
+        }
+
+        List<LauncherActivityInfo> mainActivities = getLauncherActivityInfos(ps, userId);
+        List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>();
+        for (int i = 0; i < mainActivities.size(); i++) {
+            // TODO(b/278553670) Extract and store launcher icons
+            ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
+                    mainActivities.get(i).getLabel().toString(),
+                    Path.of("/TODO"), null);
+            archiveActivityInfos.add(activityInfo);
+        }
+
+        return new ArchiveState(archiveActivityInfos, responsibleInstallerPackage);
+    }
+
+    @Override
+    public void requestUnarchive(
+            @NonNull String packageName,
+            @NonNull String callerPackageName,
+            @NonNull UserHandle userHandle) {
+        Objects.requireNonNull(packageName);
+        Objects.requireNonNull(callerPackageName);
+        Objects.requireNonNull(userHandle);
+
+        Computer snapshot = mPm.snapshotComputer();
+        int userId = userHandle.getIdentifier();
+        int binderUid = Binder.getCallingUid();
+        int providedUid = snapshot.getPackageUid(callerPackageName, 0, userId);
+        snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
+                "unarchiveApp");
+        verifyCaller(providedUid, binderUid);
+        PackageStateInternal ps;
+        try {
+            ps = getPackageState(packageName, snapshot, binderUid, userId);
+            verifyArchived(ps, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new ParcelableException(e);
+        }
+        String installerPackage = getResponsibleInstallerPackage(ps);
+        if (installerPackage == null) {
             throw new ParcelableException(
                     new PackageManager.NameNotFoundException(
-                            TextUtils.formatSimple("No installer found to archive app %s.",
+                            TextUtils.formatSimple("No installer found to unarchive app %s.",
                                     packageName)));
         }
+
+        mPm.mHandler.post(() -> unarchiveInternal(packageName, userHandle, installerPackage));
+    }
+
+    private void verifyArchived(PackageStateInternal ps, int userId)
+            throws PackageManager.NameNotFoundException {
+        PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
+        // TODO(b/288142708) Check for isInstalled false here too.
+        if (userState.getArchiveState() == null) {
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("Package %s is not currently archived.",
+                            ps.getPackageName()));
+        }
+    }
+
+    @RequiresPermission(
+            allOf = {
+                    Manifest.permission.INTERACT_ACROSS_USERS,
+                    android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
+                    android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
+                    android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND},
+            conditional = true)
+    private void unarchiveInternal(String packageName, UserHandle userHandle,
+            String installerPackage) {
+        int userId = userHandle.getIdentifier();
+        Intent unarchiveIntent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE);
+        unarchiveIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        unarchiveIntent.putExtra(PackageArchiver.EXTRA_UNARCHIVE_PACKAGE_NAME, packageName);
+        unarchiveIntent.putExtra(PackageArchiver.EXTRA_UNARCHIVE_ALL_USERS,
+                userId == UserHandle.USER_ALL);
+        unarchiveIntent.setPackage(installerPackage);
+
+        // If the unarchival is requested for all users, the current user is used for unarchival.
+        UserHandle userForUnarchival = userId == UserHandle.USER_ALL
+                ? UserHandle.of(mPm.mUserManager.getCurrentUserId())
+                : userHandle;
+        mContext.sendOrderedBroadcastAsUser(
+                unarchiveIntent,
+                userForUnarchival,
+                /* receiverPermission = */ null,
+                AppOpsManager.OP_NONE,
+                createUnarchiveOptions(),
+                /* resultReceiver= */ null,
+                /* scheduler= */ null,
+                /* initialCode= */ 0,
+                /* initialData= */ null,
+                /* initialExtras= */ null);
+    }
+
+    private List<LauncherActivityInfo> getLauncherActivityInfos(PackageStateInternal ps,
+            int userId) throws PackageManager.NameNotFoundException {
+        List<LauncherActivityInfo> mainActivities =
+                Binder.withCleanCallingIdentity(() -> getLauncherApps().getActivityList(
+                        ps.getPackageName(),
+                        new UserHandle(userId)));
+        if (mainActivities.isEmpty()) {
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("The app %s does not have a main activity.",
+                            ps.getPackageName()));
+        }
+
+        return mainActivities;
+    }
+
+    @RequiresPermission(anyOf = {android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST,
+            android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
+            android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND})
+    private Bundle createUnarchiveOptions() {
+        BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setTemporaryAppAllowlist(getUnarchiveForegroundTimeout(),
+                TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED,
+                REASON_PACKAGE_UNARCHIVE, "");
+        return options.toBundle();
+    }
+
+    private static int getUnarchiveForegroundTimeout() {
+        return DEFAULT_UNARCHIVE_FOREGROUND_TIMEOUT_MS;
+    }
+
+    private String getResponsibleInstallerPackage(PackageStateInternal ps) {
+        return ps.getInstallSource().mUpdateOwnerPackageName == null
+                ? ps.getInstallSource().mInstallerPackageName
+                : ps.getInstallSource().mUpdateOwnerPackageName;
     }
 
     @NonNull
     private static PackageStateInternal getPackageState(String packageName,
-            Computer snapshot, int callingUid, int userId) {
+            Computer snapshot, int callingUid, int userId)
+            throws PackageManager.NameNotFoundException {
         PackageStateInternal ps = snapshot.getPackageStateFiltered(packageName, callingUid,
                 userId);
         if (ps == null) {
-            throw new ParcelableException(
-                    new PackageManager.NameNotFoundException(
-                            TextUtils.formatSimple("Package %s not found.", packageName)));
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("Package %s not found.", packageName));
         }
         return ps;
     }
@@ -130,38 +271,25 @@
         return mLauncherApps;
     }
 
-    private void storeArchiveState(PackageStateInternal ps,
-            List<LauncherActivityInfo> mainActivities, int userId) {
-        List<ArchiveActivityInfo> activityInfos = new ArrayList<>();
-        for (int i = 0; i < mainActivities.size(); i++) {
-            // TODO(b/278553670) Extract and store launcher icons
-            ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
-                    mainActivities.get(i).getLabel().toString(),
-                    Path.of("/TODO"), null);
-            activityInfos.add(activityInfo);
-        }
-
-        InstallSource installSource = ps.getInstallSource();
-        String installerPackageName = installSource.mUpdateOwnerPackageName != null
-                ? installSource.mUpdateOwnerPackageName : installSource.mInstallerPackageName;
-
+    private void storeArchiveState(String packageName, ArchiveState archiveState, int userId)
+            throws PackageManager.NameNotFoundException {
         synchronized (mPm.mLock) {
-            PackageSetting packageSetting = getPackageSettingLocked(ps.getPackageName(), userId);
+            PackageSetting packageSetting = getPackageSettingLocked(packageName, userId);
             packageSetting
                     .modifyUserState(userId)
-                    .setArchiveState(new ArchiveState(activityInfos, installerPackageName));
+                    .setArchiveState(archiveState);
         }
     }
 
     @NonNull
     @GuardedBy("mPm.mLock")
-    private PackageSetting getPackageSettingLocked(String packageName, int userId) {
+    private PackageSetting getPackageSettingLocked(String packageName, int userId)
+            throws PackageManager.NameNotFoundException {
         PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
         // Shouldn't happen, we already verify presence of the package in getPackageState()
         if (ps == null || !ps.getUserStateOrDefault(userId).isInstalled()) {
-            throw new ParcelableException(
-                    new PackageManager.NameNotFoundException(
-                            TextUtils.formatSimple("Package %s not found.", packageName)));
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("Package %s not found.", packageName));
         }
         return ps;
     }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index cbac39a..63794d5 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3354,6 +3354,13 @@
                     return true;
                 }
                 break;
+            case KeyEvent.KEYCODE_DEL:
+            case KeyEvent.KEYCODE_GRAVE:
+                if (firstDown && event.isMetaPressed()) {
+                    logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
+                    injectBackGesture(event.getDownTime());
+                    return true;
+                }
             case KeyEvent.KEYCODE_DPAD_UP:
                 if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
@@ -3365,9 +3372,14 @@
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_LEFT:
-                if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
-                    enterStageSplitFromRunningApp(true /* leftOrTop */);
-                    logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+                if (firstDown && event.isMetaPressed()) {
+                    if (event.isCtrlPressed()) {
+                        enterStageSplitFromRunningApp(true /* leftOrTop */);
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION);
+                    } else {
+                        logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK);
+                        injectBackGesture(event.getDownTime());
+                    }
                     return true;
                 }
                 break;
@@ -3630,6 +3642,25 @@
         return (metaState & KeyEvent.META_META_ON) != 0;
     }
 
+    @SuppressLint("MissingPermission")
+    private void injectBackGesture(long downtime) {
+        // Create and inject down event
+        KeyEvent downEvent = new KeyEvent(downtime, downtime, KeyEvent.ACTION_DOWN,
+                KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */,
+                KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
+                KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+                InputDevice.SOURCE_KEYBOARD);
+        mInputManager.injectInputEvent(downEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+
+
+        // Create and inject up event
+        KeyEvent upEvent = KeyEvent.changeAction(downEvent, KeyEvent.ACTION_UP);
+        mInputManager.injectInputEvent(upEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+
+        downEvent.recycle();
+        upEvent.recycle();
+    }
+
     private boolean handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) {
         // First we always handle the home key here, so applications
         // can never break it, although if keyguard is on, we do let
diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
index 01fdc88..7862f58 100644
--- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java
+++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java
@@ -41,6 +41,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.AppGlobals;
@@ -62,6 +63,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -1304,6 +1306,46 @@
         return false;
     }
 
+    /**
+     * Check if the targetPkg can be granted permission to access uri by
+     * the callingUid using the given modeFlags. See {@link #checkGrantUriPermissionUnlocked}.
+     *
+     * @param callingUid The uid of the grantor app that has permissions to the uri.
+     * @param targetPkg The package name of the granted app that needs permissions to the uri.
+     * @param uri The uri for which permissions should be granted.
+     * @param modeFlags The modes to grant. See {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}, etc.
+     * @param userId The userId in which the uri is to be resolved.
+     * @return uid of the target or -1 if permission grant not required. Returns -1 if the caller
+     *  does not hold INTERACT_ACROSS_USERS_FULL
+     * @throws SecurityException if the grant is not allowed.
+     */
+    @Override
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+    public int checkGrantUriPermission_ignoreNonSystem(int callingUid, String targetPkg, Uri uri,
+            int modeFlags, int userId) {
+        if (!isCallerIsSystemOrPrivileged()) {
+            return Process.INVALID_UID;
+        }
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            return checkGrantUriPermissionUnlocked(callingUid, targetPkg, uri, modeFlags,
+                        userId);
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    private boolean isCallerIsSystemOrPrivileged() {
+        final int uid = Binder.getCallingUid();
+        if (uid == Process.SYSTEM_UID || uid == Process.ROOT_UID) {
+            return true;
+        }
+        return ActivityManager.checkComponentPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    uid, /* owningUid = */-1, /* exported = */ true)
+                    == PackageManager.PERMISSION_GRANTED;
+    }
+
     @Override
     public ArrayList<UriPermission> providePersistentUriGrants() {
         final ArrayList<UriPermission> result = new ArrayList<>();
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
index 3fb845f..e4f9607 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackCustomization.java
@@ -19,7 +19,7 @@
 import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
+import android.os.VibratorInfo;
 import android.os.vibrator.persistence.ParsedVibration;
 import android.os.vibrator.persistence.VibrationXmlParser;
 import android.text.TextUtils;
@@ -107,10 +107,10 @@
      * @hide
      */
     @Nullable
-    static SparseArray<VibrationEffect> loadVibrations(Resources res, Vibrator vibrator)
+    static SparseArray<VibrationEffect> loadVibrations(Resources res, VibratorInfo vibratorInfo)
             throws CustomizationParserException, IOException {
         try {
-            return loadVibrationsInternal(res, vibrator);
+            return loadVibrationsInternal(res, vibratorInfo);
         } catch (VibrationXmlParser.VibrationXmlParserException
                 | XmlParserException
                 | XmlPullParserException e) {
@@ -121,7 +121,7 @@
 
     @Nullable
     private static SparseArray<VibrationEffect> loadVibrationsInternal(
-            Resources res, Vibrator vibrator) throws
+            Resources res, VibratorInfo vibratorInfo) throws
                     CustomizationParserException,
                     IOException,
                     VibrationXmlParser.VibrationXmlParserException,
@@ -175,7 +175,7 @@
                 throw new CustomizationParserException(
                         "Unable to parse vibration element for effect " + effectId);
             }
-            VibrationEffect effect = parsedVibration.resolve(vibrator);
+            VibrationEffect effect = parsedVibration.resolve(vibratorInfo);
             if (effect != null) {
                 if (effect.getDuration() == Long.MAX_VALUE) {
                     throw new CustomizationParserException(String.format(
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index 7c99543..3d89afa 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -21,6 +21,7 @@
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.os.VibratorInfo;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.HapticFeedbackConstants;
@@ -29,8 +30,6 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.HashSet;
-import java.util.Set;
 
 /**
  * Provides the {@link VibrationEffect} and {@link VibrationAttributes} for haptic feedback.
@@ -47,7 +46,7 @@
     private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
             VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
 
-    private final Vibrator mVibrator;
+    private final VibratorInfo mVibratorInfo;
     private final boolean mHapticTextHandleEnabled;
     // Vibrator effect for haptic feedback during boot when safe mode is enabled.
     private final VibrationEffect mSafeModeEnabledVibrationEffect;
@@ -58,25 +57,25 @@
 
     /** @hide */
     public HapticFeedbackVibrationProvider(Resources res, Vibrator vibrator) {
-        this(res, vibrator, loadHapticCustomizations(res, vibrator));
+        this(res, vibrator.getInfo());
+    }
+
+    /** @hide */
+    public HapticFeedbackVibrationProvider(Resources res, VibratorInfo vibratorInfo) {
+        this(res, vibratorInfo, loadHapticCustomizations(res, vibratorInfo));
     }
 
     /** @hide */
     @VisibleForTesting HapticFeedbackVibrationProvider(
             Resources res,
-            Vibrator vibrator,
+            VibratorInfo vibratorInfo,
             @Nullable SparseArray<VibrationEffect> hapticCustomizations) {
-        mVibrator = vibrator;
+        mVibratorInfo = vibratorInfo;
         mHapticTextHandleEnabled = res.getBoolean(
                 com.android.internal.R.bool.config_enableHapticTextHandle);
 
-        if (hapticCustomizations != null) {
-            // Clean up the customizations to remove vibrations which may not ever be used due to
-            // Vibrator properties or other device configurations.
-            removeUnsupportedVibrations(hapticCustomizations, vibrator);
-            if (hapticCustomizations.size() == 0) {
-                hapticCustomizations = null;
-            }
+        if (hapticCustomizations != null && hapticCustomizations.size() == 0) {
+            hapticCustomizations = null;
         }
         mHapticCustomizations = hapticCustomizations;
 
@@ -257,7 +256,7 @@
         if (effectHasCustomization(hapticFeedbackId)) {
             return mHapticCustomizations.get(hapticFeedbackId);
         }
-        if (mVibrator.areAllPrimitivesSupported(primitiveId)) {
+        if (mVibratorInfo.isPrimitiveSupported(primitiveId)) {
             return VibrationEffect.startComposition()
                     .addPrimitive(primitiveId, primitiveScale)
                     .compose();
@@ -270,9 +269,8 @@
         if (effectHasCustomization(HapticFeedbackConstants.ASSISTANT_BUTTON)) {
             return mHapticCustomizations.get(HapticFeedbackConstants.ASSISTANT_BUTTON);
         }
-        if (mVibrator.areAllPrimitivesSupported(
-                VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
-                VibrationEffect.Composition.PRIMITIVE_TICK)) {
+        if (mVibratorInfo.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE)
+                && mVibratorInfo.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK)) {
             // quiet ramp, short pause, then sharp tick
             return VibrationEffect.startComposition()
                     .addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 0.25f)
@@ -289,27 +287,12 @@
 
     @Nullable
     private static SparseArray<VibrationEffect> loadHapticCustomizations(
-            Resources res, Vibrator vibrator) {
+            Resources res, VibratorInfo vibratorInfo) {
         try {
-            return HapticFeedbackCustomization.loadVibrations(res, vibrator);
+            return HapticFeedbackCustomization.loadVibrations(res, vibratorInfo);
         } catch (IOException | HapticFeedbackCustomization.CustomizationParserException e) {
             Slog.e(TAG, "Unable to load haptic customizations.", e);
             return null;
         }
     }
-
-    private static void removeUnsupportedVibrations(
-            SparseArray<VibrationEffect> customizations, Vibrator vibrator) {
-        Set<Integer> keysToRemove = new HashSet<>();
-        for (int i = 0; i < customizations.size(); i++) {
-            int key = customizations.keyAt(i);
-            if (!vibrator.areVibrationFeaturesSupported(customizations.get(key))) {
-                keysToRemove.add(key);
-            }
-        }
-
-        for (int key : keysToRemove) {
-            customizations.remove(key);
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index e296c7b..ee3d697 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -28,6 +28,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.hardware.vibrator.IVibrator;
 import android.os.BatteryStats;
 import android.os.Binder;
@@ -131,6 +132,7 @@
 
     private final Object mLock = new Object();
     private final Context mContext;
+    private final Injector mInjector;
     private final PowerManager.WakeLock mWakeLock;
     private final IBatteryStats mBatteryStatsService;
     private final VibratorFrameworkStatsLogger mFrameworkStatsLogger;
@@ -162,6 +164,8 @@
 
     @GuardedBy("mLock")
     @Nullable private VibratorInfo mCombinedVibratorInfo;
+    @GuardedBy("mLock")
+    @Nullable private HapticFeedbackVibrationProvider mHapticFeedbackVibrationProvider;
 
     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
@@ -201,6 +205,7 @@
     @VisibleForTesting
     VibratorManagerService(Context context, Injector injector) {
         mContext = context;
+        mInjector = injector;
         mHandler = injector.createHandler(Looper.myLooper());
 
         mVibrationSettings = new VibrationSettings(mContext, mHandler);
@@ -393,7 +398,42 @@
     @Override // Binder call
     public void vibrate(int uid, int displayId, String opPkg, @NonNull CombinedVibration effect,
             @Nullable VibrationAttributes attrs, String reason, IBinder token) {
-        vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token);
+        vibrateWithPermissionCheck(uid, displayId, opPkg, effect, attrs, reason, token);
+    }
+
+    @Override // Binder call
+    public void performHapticFeedback(
+            int uid, int displayId, String opPkg, int constant, boolean always, String reason,
+            IBinder token) {
+        performHapticFeedbackInternal(uid, displayId, opPkg, constant, always, reason, token);
+    }
+
+    /**
+     * An internal-only version of performHapticFeedback that allows the caller access to the
+     * {@link HalVibration}.
+     * The Vibration is only returned if it is ongoing after this method returns.
+     */
+    @VisibleForTesting
+    @Nullable
+    HalVibration performHapticFeedbackInternal(
+            int uid, int displayId, String opPkg, int constant, boolean always, String reason,
+            IBinder token) {
+        HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider();
+        if (hapticVibrationProvider == null) {
+            Slog.w(TAG, "performHapticFeedback; haptic vibration provider not ready.");
+            return null;
+        }
+        VibrationEffect effect = hapticVibrationProvider.getVibrationForHapticFeedback(constant);
+        if (effect == null) {
+            Slog.w(TAG, "performHapticFeedback; vibration absent for effect " + constant);
+            return null;
+        }
+        CombinedVibration combinedVibration = CombinedVibration.createParallel(effect);
+        VibrationAttributes attrs =
+                hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
+                        constant, /* bypassVibrationIntensitySetting= */ always);
+        return vibrateWithoutPermissionCheck(uid, displayId, opPkg, combinedVibration, attrs,
+                "performHapticFeedback: " + reason, token);
     }
 
     /**
@@ -403,93 +443,110 @@
      */
     @VisibleForTesting
     @Nullable
-    HalVibration vibrateInternal(int uid, int displayId, String opPkg,
+    HalVibration vibrateWithPermissionCheck(int uid, int displayId, String opPkg,
             @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs,
             String reason, IBinder token) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
         try {
-            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.VIBRATE, "vibrate");
-
-            if (token == null) {
-                Slog.e(TAG, "token must not be null");
-                return null;
-            }
-            enforceUpdateAppOpsStatsPermission(uid);
-            if (!isEffectValid(effect)) {
-                return null;
-            }
-            attrs = fixupVibrationAttributes(attrs, effect);
-            // Create Vibration.Stats as close to the received request as possible, for tracking.
-            HalVibration vib = new HalVibration(token, effect,
-                    new Vibration.CallerInfo(attrs, uid, displayId, opPkg, reason));
-            fillVibrationFallbacks(vib, effect);
-
-            if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
-                // Force update of user settings before checking if this vibration effect should
-                // be ignored or scaled.
-                mVibrationSettings.update();
-            }
-
-            synchronized (mLock) {
-                if (DEBUG) {
-                    Slog.d(TAG, "Starting vibrate for vibration " + vib.id);
-                }
-
-                // Check if user settings or DnD is set to ignore this vibration.
-                Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
-
-                // Check if ongoing vibration is more important than this vibration.
-                if (vibrationEndInfo == null) {
-                    vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vib);
-                }
-
-                // If not ignored so far then try to start this vibration.
-                if (vibrationEndInfo == null) {
-                    final long ident = Binder.clearCallingIdentity();
-                    try {
-                        if (mCurrentExternalVibration != null) {
-                            mCurrentExternalVibration.mute();
-                            vib.stats.reportInterruptedAnotherVibration(
-                                    mCurrentExternalVibration.callerInfo);
-                            endExternalVibrateLocked(
-                                    new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
-                                            vib.callerInfo),
-                                    /* continueExternalControl= */ false);
-                        } else if (mCurrentVibration != null) {
-                            if (mCurrentVibration.getVibration().canPipelineWith(vib)) {
-                                // Don't cancel the current vibration if it's pipeline-able.
-                                // Note that if there is a pending next vibration that can't be
-                                // pipelined, it will have already cancelled the current one, so we
-                                // don't need to consider it here as well.
-                                if (DEBUG) {
-                                    Slog.d(TAG, "Pipelining vibration " + vib.id);
-                                }
-                            } else {
-                                vib.stats.reportInterruptedAnotherVibration(
-                                        mCurrentVibration.getVibration().callerInfo);
-                                mCurrentVibration.notifyCancelled(
-                                        new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
-                                                vib.callerInfo),
-                                        /* immediate= */ false);
-                            }
-                        }
-                        vibrationEndInfo = startVibrationLocked(vib);
-                    } finally {
-                        Binder.restoreCallingIdentity(ident);
-                    }
-                }
-
-                // Ignored or failed to start the vibration, end it and report metrics right away.
-                if (vibrationEndInfo != null) {
-                    endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ true);
-                }
-                return vib;
-            }
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.VIBRATE, "vibrate");
+            return vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
         }
     }
 
+    HalVibration vibrateWithoutPermissionCheck(int uid, int displayId, String opPkg,
+            @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs,
+            String reason, IBinder token) {
+        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate no perm check, reason = " + reason);
+        try {
+            return vibrateInternal(uid, displayId, opPkg, effect, attrs, reason, token);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
+        }
+    }
+
+    private HalVibration vibrateInternal(int uid, int displayId, String opPkg,
+            @NonNull CombinedVibration effect, @Nullable VibrationAttributes attrs,
+            String reason, IBinder token) {
+        if (token == null) {
+            Slog.e(TAG, "token must not be null");
+            return null;
+        }
+        enforceUpdateAppOpsStatsPermission(uid);
+        if (!isEffectValid(effect)) {
+            return null;
+        }
+        attrs = fixupVibrationAttributes(attrs, effect);
+        // Create Vibration.Stats as close to the received request as possible, for tracking.
+        HalVibration vib = new HalVibration(token, effect,
+                new Vibration.CallerInfo(attrs, uid, displayId, opPkg, reason));
+        fillVibrationFallbacks(vib, effect);
+
+        if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
+            // Force update of user settings before checking if this vibration effect should
+            // be ignored or scaled.
+            mVibrationSettings.update();
+        }
+
+        synchronized (mLock) {
+            if (DEBUG) {
+                Slog.d(TAG, "Starting vibrate for vibration " + vib.id);
+            }
+
+            // Check if user settings or DnD is set to ignore this vibration.
+            Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
+
+            // Check if ongoing vibration is more important than this vibration.
+            if (vibrationEndInfo == null) {
+                vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vib);
+            }
+
+            // If not ignored so far then try to start this vibration.
+            if (vibrationEndInfo == null) {
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    if (mCurrentExternalVibration != null) {
+                        mCurrentExternalVibration.mute();
+                        vib.stats.reportInterruptedAnotherVibration(
+                                mCurrentExternalVibration.callerInfo);
+                        endExternalVibrateLocked(
+                                new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+                                        vib.callerInfo),
+                                /* continueExternalControl= */ false);
+                    } else if (mCurrentVibration != null) {
+                        if (mCurrentVibration.getVibration().canPipelineWith(vib)) {
+                            // Don't cancel the current vibration if it's pipeline-able.
+                            // Note that if there is a pending next vibration that can't be
+                            // pipelined, it will have already cancelled the current one, so we
+                            // don't need to consider it here as well.
+                            if (DEBUG) {
+                                Slog.d(TAG, "Pipelining vibration " + vib.id);
+                            }
+                        } else {
+                            vib.stats.reportInterruptedAnotherVibration(
+                                    mCurrentVibration.getVibration().callerInfo);
+                            mCurrentVibration.notifyCancelled(
+                                    new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
+                                            vib.callerInfo),
+                                    /* immediate= */ false);
+                        }
+                    }
+                    vibrationEndInfo = startVibrationLocked(vib);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+
+            // Ignored or failed to start the vibration, end it and report metrics right away.
+            if (vibrationEndInfo != null) {
+                endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ true);
+            }
+            return vib;
+        }
+    }
+
     @Override // Binder call
     public void cancelVibrate(int usageFilter, IBinder token) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "cancelVibrate");
@@ -1315,6 +1372,11 @@
             return new VibratorController(vibratorId, listener);
         }
 
+        HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
+                Resources resources, VibratorInfo vibratorInfo) {
+            return new HapticFeedbackVibrationProvider(resources, vibratorInfo);
+        }
+
         void addService(String name, IBinder service) {
             ServiceManager.addService(name, service);
         }
@@ -1831,6 +1893,22 @@
         }
     }
 
+    private HapticFeedbackVibrationProvider getHapticVibrationProvider() {
+        synchronized (mLock) {
+            // Used a cached haptic vibration provider if one exists.
+            if (mHapticFeedbackVibrationProvider != null) {
+                return mHapticFeedbackVibrationProvider;
+            }
+            VibratorInfo combinedVibratorInfo = getCombinedVibratorInfo();
+            if (combinedVibratorInfo == null) {
+                return null;
+            }
+            return mHapticFeedbackVibrationProvider =
+                    mInjector.createHapticFeedbackVibrationProvider(
+                            mContext.getResources(), combinedVibratorInfo);
+        }
+    }
+
     private VibratorInfo getCombinedVibratorInfo() {
         synchronized (mLock) {
             // Used a cached resolving vibrator if one exists.
@@ -2068,6 +2146,9 @@
                 if ("cancel".equals(cmd)) {
                     return runCancel();
                 }
+                if ("feedback".equals(cmd)) {
+                    return runHapticFeedback();
+                }
                 return handleDefaultCommands(cmd);
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -2098,16 +2179,10 @@
             // only cancel background vibrations.
             IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
                     : mShellCallbacksToken;
-            HalVibration vib = vibrateInternal(Binder.getCallingUid(), Display.DEFAULT_DISPLAY,
-                    SHELL_PACKAGE_NAME, combined, attrs, commonOptions.description, deathBinder);
-            if (vib != null && !commonOptions.background) {
-                try {
-                    // Waits for the client vibration to finish, but the VibrationThread may still
-                    // do cleanup after this.
-                    vib.waitForEnd();
-                } catch (InterruptedException e) {
-                }
-            }
+            HalVibration vib = vibrateWithPermissionCheck(Binder.getCallingUid(),
+                    Display.DEFAULT_DISPLAY, SHELL_PACKAGE_NAME, combined, attrs,
+                    commonOptions.description, deathBinder);
+            maybeWaitOnVibration(vib, commonOptions);
         }
 
         private int runMono() {
@@ -2155,6 +2230,21 @@
             return 0;
         }
 
+        private int runHapticFeedback() {
+            CommonOptions commonOptions = new CommonOptions();
+            int constant = Integer.parseInt(getNextArgRequired());
+
+            IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
+                    : mShellCallbacksToken;
+            HalVibration vib = performHapticFeedbackInternal(Binder.getCallingUid(),
+                    Display.DEFAULT_DISPLAY, SHELL_PACKAGE_NAME, constant,
+                    /* always= */ commonOptions.force, /* reason= */ commonOptions.description,
+                    deathBinder);
+            maybeWaitOnVibration(vib, commonOptions);
+
+            return 0;
+        }
+
         private VibrationEffect nextEffect() {
             VibrationEffect.Composition composition = VibrationEffect.startComposition();
             String nextArg;
@@ -2364,6 +2454,17 @@
             }
         }
 
+        private void maybeWaitOnVibration(HalVibration vib, CommonOptions commonOptions) {
+            if (vib != null && !commonOptions.background) {
+                try {
+                    // Waits for the client vibration to finish, but the VibrationThread may still
+                    // do cleanup after this.
+                    vib.waitForEnd();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
         @Override
         public void onHelp() {
             try (PrintWriter pw = getOutPrintWriter();) {
@@ -2389,6 +2490,10 @@
                 pw.println("    XML containing a single effect it runs on all vibrators in sync.");
                 pw.println("  cancel");
                 pw.println("    Cancels any active vibration");
+                pw.println("  feedback [-f] [-d <description>] <constant>");
+                pw.println("    Performs a haptic feedback with the given constant.");
+                pw.println("    The force (-f) option enables the `always` configuration, which");
+                pw.println("    plays the haptic irrespective of the vibration intensity settings");
                 pw.println("");
                 pw.println("Effect commands:");
                 pw.println("  oneshot [-w delay] [-a] <duration> [<amplitude>]");
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 53d1adf..fd42077 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -6183,6 +6183,8 @@
         @Override
         public void onPackageReplaced(ApplicationInfo aInfo) {
             synchronized (mGlobalLock) {
+                // In case if setWindowManager hasn't been called yet when booting.
+                if (mRootWindowContainer == null) return;
                 mRootWindowContainer.updateActivityApplicationInfo(aInfo);
             }
         }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 1871cf6..707b779 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
 import static android.view.Display.TYPE_INTERNAL;
 import static android.view.InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE;
 import static android.view.InsetsFrameProvider.SOURCE_CONTAINER_BOUNDS;
@@ -1201,8 +1202,8 @@
                 throw new IllegalArgumentException("IME insets must be provided by a window.");
             }
 
-            if (mNavigationBar != null && navigationBarPosition(displayFrames.mRotation)
-                    == NAV_BAR_BOTTOM) {
+            if (!ENABLE_HIDE_IME_CAPTION_BAR && mNavigationBar != null
+                    && navigationBarPosition(displayFrames.mRotation) == NAV_BAR_BOTTOM) {
                 // In gesture navigation, nav bar frame is larger than frame to calculate insets.
                 // IME should not provide frame which is smaller than the nav bar frame. Otherwise,
                 // nav bar might be overlapped with the content of the client when IME is shown.
diff --git a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
index e646f14..106e142 100644
--- a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
+++ b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.util.Slog;
 import android.view.IDisplayChangeWindowCallback;
 import android.window.DisplayAreaInfo;
@@ -40,6 +41,7 @@
 public class RemoteDisplayChangeController {
 
     private static final String TAG = "RemoteDisplayChangeController";
+    private static final String REMOTE_DISPLAY_CHANGE_TRACE_TAG = "RemoteDisplayChange";
 
     private static final int REMOTE_DISPLAY_CHANGE_TIMEOUT_MS = 800;
 
@@ -82,6 +84,10 @@
         }
         mCallbacks.add(callback);
 
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+            Trace.beginAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+        }
+
         if (newDisplayAreaInfo != null) {
             ProtoLog.v(WM_DEBUG_CONFIGURATION,
                     "Starting remote display change: "
@@ -122,6 +128,10 @@
                     mCallbacks.clear();
                 }
                 callback.onContinueRemoteDisplayChange(null /* transaction */);
+
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+                    Trace.endAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+                }
             }
         }
     }
@@ -137,13 +147,23 @@
             for (int i = 0; i < idx; ++i) {
                 // Expect remote callbacks in order. If they don't come in order, then force
                 // ordering by continuing everything up until this one with empty transactions.
-                mCallbacks.get(i).onContinueRemoteDisplayChange(null /* transaction */);
+                ContinueRemoteDisplayChangeCallback currentCallback = mCallbacks.get(i);
+                currentCallback.onContinueRemoteDisplayChange(null /* transaction */);
+
+                if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+                    Trace.endAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG,
+                            currentCallback.hashCode());
+                }
             }
             mCallbacks.subList(0, idx + 1).clear();
             if (mCallbacks.isEmpty()) {
                 mService.mH.removeCallbacks(mTimeoutRunnable);
             }
             callback.onContinueRemoteDisplayChange(transaction);
+
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+                Trace.endAsyncSection(REMOTE_DISPLAY_CHANGE_TRACE_TAG, callback.hashCode());
+            }
         }
     }
 
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 2a995b2..ec5378f 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -41,6 +41,7 @@
         "com_android_server_companion_virtual_InputController.cpp",
         "com_android_server_devicepolicy_CryptoTestHelper.cpp",
         "com_android_server_display_DisplayControl.cpp",
+        "com_android_server_display_SmallAreaDetectionController.cpp",
         "com_android_server_connectivity_Vpn.cpp",
         "com_android_server_gpu_GpuService.cpp",
         "com_android_server_HardwarePropertiesManagerService.cpp",
diff --git a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
new file mode 100644
index 0000000..b256f16
--- /dev/null
+++ b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "SmallAreaDetectionController"
+
+#include <gui/SurfaceComposerClient.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+
+#include "jni.h"
+#include "utils/Log.h"
+
+namespace android {
+static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray juids,
+                                           jfloatArray jthresholds) {
+    if (juids == nullptr || jthresholds == nullptr) return;
+
+    ScopedIntArrayRO uids(env, juids);
+    ScopedFloatArrayRO thresholds(env, jthresholds);
+
+    if (uids.size() != thresholds.size()) {
+        ALOGE("uids size exceeds thresholds size!");
+        return;
+    }
+
+    std::vector<int32_t> uidVector;
+    std::vector<float> thresholdVector;
+    size_t size = uids.size();
+    uidVector.reserve(size);
+    thresholdVector.reserve(size);
+    for (int i = 0; i < size; i++) {
+        uidVector.push_back(static_cast<int32_t>(uids[i]));
+        thresholdVector.push_back(static_cast<float>(thresholds[i]));
+    }
+    SurfaceComposerClient::updateSmallAreaDetection(uidVector, thresholdVector);
+}
+
+static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint uid,
+                                                 jfloat threshold) {
+    SurfaceComposerClient::setSmallAreaDetectionThreshold(uid, threshold);
+}
+
+static const JNINativeMethod gMethods[] = {
+        {"nativeUpdateSmallAreaDetection", "([I[F)V", (void*)nativeUpdateSmallAreaDetection},
+        {"nativeSetSmallAreaDetectionThreshold", "(IF)V",
+         (void*)nativeSetSmallAreaDetectionThreshold},
+};
+
+int register_android_server_display_smallAreaDetectionController(JNIEnv* env) {
+    return jniRegisterNativeMethods(env, "com/android/server/display/SmallAreaDetectionController",
+                                    gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 97d7be6..f6f6737 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -67,6 +67,7 @@
 int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
 int register_com_android_server_display_DisplayControl(JNIEnv* env);
 int register_com_android_server_SystemClockTime(JNIEnv* env);
+int register_android_server_display_smallAreaDetectionController(JNIEnv* env);
 };
 
 using namespace android;
@@ -126,5 +127,6 @@
     register_com_android_server_wm_TaskFpsCallbackController(env);
     register_com_android_server_display_DisplayControl(env);
     register_com_android_server_SystemClockTime(env);
+    register_android_server_display_smallAreaDetectionController(env);
     return JNI_VERSION_1_4;
 }
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
index fb14419..e28028f9 100644
--- a/services/tests/displayservicetests/Android.bp
+++ b/services/tests/displayservicetests/Android.bp
@@ -35,6 +35,7 @@
         "mockingservicestests-utils-mockito",
         "platform-compat-test-rules",
         "platform-test-annotations",
+        "service-permission.stubs.system_server",
         "services.core",
         "servicestests-utils",
         "testables",
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 40d7a77..a23539e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -64,6 +64,7 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.res.Resources;
 import android.graphics.Insets;
 import android.graphics.Rect;
@@ -113,6 +114,7 @@
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.lights.LightsManager;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.sensors.SensorManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -291,10 +293,11 @@
     @Mock LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy;
     @Mock IBinder mMockDisplayToken;
     @Mock SensorManagerInternal mMockSensorManagerInternal;
-
     @Mock SensorManager mSensorManager;
-
     @Mock DisplayDeviceConfig mMockDisplayDeviceConfig;
+    @Mock PackageManagerInternal mMockPackageManagerInternal;
+    @Mock UserManagerInternal mMockUserManagerInternal;
+
 
     @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
     @Mock DisplayManagerFlags mMockFlags;
@@ -315,6 +318,10 @@
         LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
         LocalServices.addService(
                 VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal);
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+        LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
         // TODO: b/287945043
         mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         mResources = Mockito.spy(mContext.getResources());
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
new file mode 100644
index 0000000..1ce79a5
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static android.os.Process.INVALID_UID;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContextWrapper;
+import android.content.pm.PackageManagerInternal;
+import android.provider.DeviceConfigInterface;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SmallAreaDetectionControllerTest {
+
+    @Rule
+    public MockitoRule mRule = MockitoJUnit.rule();
+
+    @Mock
+    private PackageManagerInternal mMockPackageManagerInternal;
+    @Mock
+    private UserManagerInternal mMockUserManagerInternal;
+
+    private SmallAreaDetectionController mSmallAreaDetectionController;
+
+    private static final String PKG_A = "com.a.b.c";
+    private static final String PKG_B = "com.d.e.f";
+    private static final String PKG_NOT_INSTALLED = "com.not.installed";
+    private static final float THRESHOLD_A = 0.05f;
+    private static final float THRESHOLD_B = 0.07f;
+    private static final int USER_1 = 110;
+    private static final int USER_2 = 111;
+    private static final int UID_A_1 = 11011111;
+    private static final int UID_A_2 = 11111111;
+    private static final int UID_B_1 = 11022222;
+    private static final int UID_B_2 = 11122222;
+
+    @Before
+    public void setup() {
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
+        LocalServices.removeServiceForTest(UserManagerInternal.class);
+        LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
+
+        when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[]{USER_1, USER_2});
+        when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_1)).thenReturn(UID_A_1);
+        when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_2)).thenReturn(UID_A_2);
+        when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_1)).thenReturn(UID_B_1);
+        when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_2)).thenReturn(UID_B_2);
+        when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_1)).thenReturn(
+                INVALID_UID);
+        when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_2)).thenReturn(
+                INVALID_UID);
+
+        mSmallAreaDetectionController = spy(new SmallAreaDetectionController(
+                new ContextWrapper(ApplicationProvider.getApplicationContext()),
+                DeviceConfigInterface.REAL));
+        doNothing().when(mSmallAreaDetectionController).updateSmallAreaDetection(any(), any());
+    }
+
+    @Test
+    public void testUpdateAllowlist_validProperty() {
+        final String property = PKG_A + ":" + THRESHOLD_A + "," + PKG_B + ":" + THRESHOLD_B;
+        mSmallAreaDetectionController.updateAllowlist(property);
+
+        final int[] resultUidArray = {UID_A_1, UID_B_1, UID_A_2, UID_B_2};
+        final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B, THRESHOLD_A, THRESHOLD_B};
+        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+                eq(resultThresholdArray));
+    }
+
+    @Test
+    public void testUpdateAllowlist_includeInvalidRow() {
+        final String property = PKG_A + "," + PKG_B + ":" + THRESHOLD_B;
+        mSmallAreaDetectionController.updateAllowlist(property);
+
+        final int[] resultUidArray = {UID_B_1, UID_B_2};
+        final float[] resultThresholdArray = {THRESHOLD_B, THRESHOLD_B};
+        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+                eq(resultThresholdArray));
+    }
+
+    @Test
+    public void testUpdateAllowlist_includeNotInstalledPkg() {
+        final String property =
+                PKG_A + ":" + THRESHOLD_A + "," + PKG_NOT_INSTALLED + ":" + THRESHOLD_B;
+        mSmallAreaDetectionController.updateAllowlist(property);
+
+        final int[] resultUidArray = {UID_A_1, UID_A_2};
+        final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_A};
+        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+                eq(resultThresholdArray));
+    }
+
+    @Test
+    public void testUpdateAllowlist_invalidProperty() {
+        final String property = PKG_A;
+        mSmallAreaDetectionController.updateAllowlist(property);
+
+        verify(mSmallAreaDetectionController, never()).updateSmallAreaDetection(any(), any());
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
index c7e1bda..e58a234 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
@@ -16,41 +16,51 @@
 
 package com.android.server.pm;
 
+import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.AppOpsManager;
 import android.content.Context;
+import android.content.Intent;
 import android.content.IntentSender;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
+import android.content.pm.PackageArchiver;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.os.Binder;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.ParcelableException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.text.TextUtils;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.pm.pkg.ArchiveState;
 import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageUserStateImpl;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -64,7 +74,8 @@
 public class PackageArchiverServiceTest {
 
     private static final String PACKAGE = "com.example";
-    private static final String CALLER_PACKAGE = "com.vending";
+    private static final String CALLER_PACKAGE = "com.caller";
+    private static final String INSTALLER_PACKAGE = "com.installer";
 
     @Rule
     public final MockSystemRule mMockSystem = new MockSystemRule();
@@ -84,11 +95,11 @@
 
     private final InstallSource mInstallSource =
             InstallSource.create(
-                    CALLER_PACKAGE,
-                    CALLER_PACKAGE,
-                    CALLER_PACKAGE,
+                    INSTALLER_PACKAGE,
+                    INSTALLER_PACKAGE,
+                    INSTALLER_PACKAGE,
                     Binder.getCallingUid(),
-                    CALLER_PACKAGE,
+                    INSTALLER_PACKAGE,
                     /* installerAttributionTag= */ null,
                     /* packageSource= */ 0);
 
@@ -96,6 +107,8 @@
 
     private final int mUserId = UserHandle.CURRENT.getIdentifier();
 
+    private PackageUserStateImpl mUserState;
+
     private PackageSetting mPackageSetting;
 
     private PackageArchiverService mArchiveService;
@@ -116,11 +129,16 @@
 
         when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
                 mPackageState);
+        when(mComputer.getPackageStateFiltered(eq(INSTALLER_PACKAGE), anyInt(),
+                anyInt())).thenReturn(mock(PackageStateInternal.class));
         when(mPackageState.getPackageName()).thenReturn(PACKAGE);
         when(mPackageState.getInstallSource()).thenReturn(mInstallSource);
         mPackageSetting = createBasicPackageSetting();
         when(mMockSystem.mocks().getSettings().getPackageLPr(eq(PACKAGE))).thenReturn(
                 mPackageSetting);
+        mUserState = new PackageUserStateImpl().setInstalled(true);
+        mPackageSetting.setUserState(mUserId, mUserState);
+        when(mPackageState.getUserStateOrDefault(eq(mUserId))).thenReturn(mUserState);
         when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
         when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
                 mLauncherActivityInfos);
@@ -135,9 +153,7 @@
         Exception e = assertThrows(
                 SecurityException.class,
                 () -> mArchiveService.requestArchive(PACKAGE, "different", mIntentSender,
-                        UserHandle.CURRENT
-                )
-        );
+                        UserHandle.CURRENT));
         assertThat(e).hasMessageThat().isEqualTo(
                 String.format(
                         "The UID %s of callerPackageName set by the caller doesn't match the "
@@ -154,9 +170,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT
-                )
-        );
+                        UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 String.format("Package %s not found.", PACKAGE));
@@ -169,9 +183,7 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT
-                )
-        );
+                        UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 String.format("Package %s not found.", PACKAGE));
@@ -193,25 +205,28 @@
         Exception e = assertThrows(
                 ParcelableException.class,
                 () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
-                        UserHandle.CURRENT
-                )
-        );
+                        UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
                 String.format("No installer found to archive app %s.", PACKAGE));
     }
 
     @Test
-    public void archiveApp_success() {
-        List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
-        for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
-            // TODO(b/278553670) Extract and store launcher icons
-            ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(
-                    mainActivity.getLabel().toString(),
-                    Path.of("/TODO"), null);
-            activityInfos.add(activityInfo);
-        }
+    public void archiveApp_noMainActivities() {
+        when(mLauncherApps.getActivityList(eq(PACKAGE), eq(UserHandle.CURRENT))).thenReturn(
+                List.of());
 
+        Exception e = assertThrows(
+                ParcelableException.class,
+                () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+                        UserHandle.CURRENT));
+        assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+        assertThat(e.getCause()).hasMessageThat().isEqualTo(
+                TextUtils.formatSimple("The app %s does not have a main activity.", PACKAGE));
+    }
+
+    @Test
+    public void archiveApp_success() {
         mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
 
         verify(mInstallerService).uninstall(
@@ -220,7 +235,112 @@
                 eq(UserHandle.CURRENT.getIdentifier()));
         assertThat(mPackageSetting.readUserState(
                 UserHandle.CURRENT.getIdentifier()).getArchiveState()).isEqualTo(
-                new ArchiveState(activityInfos, CALLER_PACKAGE));
+                createArchiveState());
+    }
+
+    @Test
+    public void unarchiveApp_callerPackageNameIncorrect() {
+        mUserState.setArchiveState(createArchiveState()).setInstalled(false);
+
+        Exception e = assertThrows(
+                SecurityException.class,
+                () -> mArchiveService.requestUnarchive(PACKAGE, "different",
+                        UserHandle.CURRENT));
+        assertThat(e).hasMessageThat().isEqualTo(
+                String.format(
+                        "The UID %s of callerPackageName set by the caller doesn't match the "
+                                + "caller's actual UID %s.",
+                        0,
+                        Binder.getCallingUid()));
+    }
+
+    @Test
+    public void unarchiveApp_packageNotInstalled() {
+        mUserState.setArchiveState(createArchiveState()).setInstalled(false);
+        when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
+                null);
+
+        Exception e = assertThrows(
+                ParcelableException.class,
+                () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE,
+                        UserHandle.CURRENT));
+        assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+        assertThat(e.getCause()).hasMessageThat().isEqualTo(
+                String.format("Package %s not found.", PACKAGE));
+    }
+
+    @Test
+    public void unarchiveApp_notArchived() {
+        Exception e = assertThrows(
+                ParcelableException.class,
+                () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE,
+                        UserHandle.CURRENT));
+        assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+        assertThat(e.getCause()).hasMessageThat().isEqualTo(
+                String.format("Package %s is not currently archived.", PACKAGE));
+    }
+
+    @Test
+    public void unarchiveApp_noInstallerFound() {
+        mUserState.setArchiveState(createArchiveState());
+        InstallSource otherInstallSource =
+                InstallSource.create(
+                        CALLER_PACKAGE,
+                        CALLER_PACKAGE,
+                        /* installerPackageName= */ null,
+                        Binder.getCallingUid(),
+                        /* updateOwnerPackageName= */ null,
+                        /* installerAttributionTag= */ null,
+                        /* packageSource= */ 0);
+        when(mPackageState.getInstallSource()).thenReturn(otherInstallSource);
+
+        Exception e = assertThrows(
+                ParcelableException.class,
+                () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE,
+                        UserHandle.CURRENT));
+        assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
+        assertThat(e.getCause()).hasMessageThat().isEqualTo(
+                String.format("No installer found to unarchive app %s.", PACKAGE));
+    }
+
+    @Test
+    public void unarchiveApp_success() {
+        mUserState.setArchiveState(createArchiveState()).setInstalled(false);
+
+        mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT);
+        mMockSystem.mocks().getHandler().flush();
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendOrderedBroadcastAsUser(
+                intentCaptor.capture(),
+                eq(UserHandle.CURRENT),
+                /* receiverPermission = */ isNull(),
+                eq(AppOpsManager.OP_NONE),
+                any(Bundle.class),
+                /* resultReceiver= */ isNull(),
+                /* scheduler= */ isNull(),
+                /* initialCode= */ eq(0),
+                /* initialData= */ isNull(),
+                /* initialExtras= */ isNull());
+        Intent intent = intentCaptor.getValue();
+        assertThat(intent.getFlags() & FLAG_RECEIVER_FOREGROUND).isNotEqualTo(0);
+        assertThat(intent.getStringExtra(PackageArchiver.EXTRA_UNARCHIVE_PACKAGE_NAME)).isEqualTo(
+                PACKAGE);
+        assertThat(
+                intent.getBooleanExtra(PackageArchiver.EXTRA_UNARCHIVE_ALL_USERS, true)).isFalse();
+        assertThat(intent.getPackage()).isEqualTo(INSTALLER_PACKAGE);
+    }
+
+    private static ArchiveState createArchiveState() {
+        List<ArchiveState.ArchiveActivityInfo> activityInfos = new ArrayList<>();
+        for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
+            // TODO(b/278553670) Extract and store launcher icons
+            ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(
+                    mainActivity.getLabel().toString(),
+                    Path.of("/TODO"), null);
+            activityInfos.add(activityInfo);
+        }
+        return new ArchiveState(activityInfos, INSTALLER_PACKAGE);
     }
 
     private static List<LauncherActivityInfo> createLauncherActivities() {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
index 10b49c6..bc826a3 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackCustomizationTest.java
@@ -30,7 +30,6 @@
 
 import android.content.res.Resources;
 import android.os.VibrationEffect;
-import android.os.Vibrator;
 import android.os.VibratorInfo;
 import android.util.AtomicFile;
 import android.util.SparseArray;
@@ -73,12 +72,10 @@
             VibrationEffect.createWaveform(new long[] {123}, new int[] {254}, -1);
 
     @Mock private Resources mResourcesMock;
-    @Mock private Vibrator mVibratorMock;
     @Mock private VibratorInfo mVibratorInfoMock;
 
     @Before
     public void setUp() {
-        when(mVibratorMock.getInfo()).thenReturn(mVibratorInfoMock);
         when(mVibratorInfoMock.areVibrationFeaturesSupported(any())).thenReturn(true);
     }
 
@@ -220,17 +217,17 @@
     public void testParseCustomizations_noCustomizationFile_returnsNull() throws Exception {
         setCustomizationFilePath("");
 
-        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock))
+        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
                 .isNull();
 
         setCustomizationFilePath(null);
 
-        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock))
+        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
                 .isNull();
 
         setCustomizationFilePath("non_existent_file.xml");
 
-        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock))
+        assertThat(HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock))
                 .isNull();
     }
 
@@ -387,7 +384,7 @@
             String xml, SparseArray<VibrationEffect> expectedCustomizations) throws Exception {
         setupCustomizationFile(xml);
         assertThat(expectedCustomizations.contentEquals(
-                HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock)))
+                HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorInfoMock)))
                         .isTrue();
     }
 
@@ -395,13 +392,15 @@
         setupCustomizationFile(xml);
         assertThrows("Expected haptic feedback customization to fail for " + xml,
                 CustomizationParserException.class,
-                () ->  HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock));
+                () ->  HapticFeedbackCustomization.loadVibrations(
+                        mResourcesMock, mVibratorInfoMock));
     }
 
     private void assertParseCustomizationsFails() throws Exception {
         assertThrows("Expected haptic feedback customization to fail",
                 CustomizationParserException.class,
-                () ->  HapticFeedbackCustomization.loadVibrations(mResourcesMock, mVibratorMock));
+                () ->  HapticFeedbackCustomization.loadVibrations(
+                        mResourcesMock, mVibratorInfoMock));
     }
 
     private void setupCustomizationFile(String xml) throws Exception {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index cae811e..a91bd2b 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -31,8 +31,10 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.hardware.vibrator.IVibrator;
+import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
-import android.os.test.FakeVibrator;
+import android.os.VibratorInfo;
 import android.util.AtomicFile;
 import android.util.SparseArray;
 
@@ -58,7 +60,7 @@
             VibrationEffect.startComposition().addPrimitive(PRIMITIVE_CLICK, 0.3497f).compose();
 
     private Context mContext = InstrumentationRegistry.getContext();
-    private FakeVibrator mVibrator = new FakeVibrator(mContext);
+    private VibratorInfo mVibratorInfo = VibratorInfo.EMPTY_VIBRATOR_INFO;
 
     @Mock private Resources mResourcesMock;
 
@@ -66,14 +68,14 @@
     public void testNonExistentCustomization_useDefault() throws Exception {
         // No customization file is set.
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator);
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
                 .isEqualTo(VibrationEffect.get(EFFECT_TICK));
 
         // The customization file specifies no customization.
         setupCustomizationFile("<haptic-feedback-constants></haptic-feedback-constants>");
-        hapticProvider = new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator);
+        hapticProvider = new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
                 .isEqualTo(VibrationEffect.get(EFFECT_TICK));
@@ -83,7 +85,7 @@
     public void testExceptionParsingCustomizations_useDefault() throws Exception {
         setupCustomizationFile("<bad-xml></bad-xml>");
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator);
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
                 .isEqualTo(VibrationEffect.get(EFFECT_TICK));
@@ -96,7 +98,7 @@
         customizations.put(CONTEXT_CLICK, PRIMITIVE_CLICK_EFFECT);
 
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
 
         // The override for `CONTEXT_CLICK` is used.
         assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
@@ -109,11 +111,15 @@
     @Test
     public void testDoNotUseInvalidCustomizedVibration() throws Exception {
         mockVibratorPrimitiveSupport(new int[] {});
-        SparseArray<VibrationEffect> customizations = new SparseArray<>();
-        customizations.put(CONTEXT_CLICK, PRIMITIVE_CLICK_EFFECT);
+        String xml = "<haptic-feedback-constants>"
+                + "<constant id=\"" + CONTEXT_CLICK + "\">"
+                + PRIMITIVE_CLICK_EFFECT
+                + "</constant>"
+                + "</haptic-feedback-constants>";
+        setupCustomizationFile(xml);
 
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
 
         // The override for `CONTEXT_CLICK` is not used because the vibration is not supported.
         assertThat(hapticProvider.getVibrationForHapticFeedback(CONTEXT_CLICK))
@@ -132,14 +138,14 @@
 
         // Test with a customization available for `TEXT_HANDLE_MOVE`.
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();
 
         // Test with no customization available for `TEXT_HANDLE_MOVE`.
         hapticProvider =
                 new HapticFeedbackVibrationProvider(
-                        mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
+                        mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE)).isNull();
     }
@@ -153,7 +159,7 @@
 
         // Test with a customization available for `TEXT_HANDLE_MOVE`.
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
                 .isEqualTo(PRIMITIVE_CLICK_EFFECT);
@@ -161,7 +167,7 @@
         // Test with no customization available for `TEXT_HANDLE_MOVE`.
         hapticProvider =
                 new HapticFeedbackVibrationProvider(
-                        mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
+                        mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(TEXT_HANDLE_MOVE))
                 .isEqualTo(VibrationEffect.get(EFFECT_TEXTURE_TICK));
@@ -176,14 +182,14 @@
         customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
 
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
                 .isEqualTo(PRIMITIVE_CLICK_EFFECT);
 
         mockSafeModeEnabledVibration(null);
         hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo, customizations);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
                 .isEqualTo(PRIMITIVE_CLICK_EFFECT);
@@ -193,20 +199,9 @@
     public void testNoValidCustomizationPresentForSafeModeEnabled_resourceBasedVibrationUsed()
                 throws Exception {
         mockSafeModeEnabledVibration(10, 20, 30, 40);
-        SparseArray<VibrationEffect> customizations = new SparseArray<>();
-        customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
-
-        // Test with a customization that is not supported by the vibrator.
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
-
-        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
-                .isEqualTo(VibrationEffect.createWaveform(new long[] {10, 20, 30, 40}, -1));
-
-        // Test with no customizations.
-        hapticProvider =
                 new HapticFeedbackVibrationProvider(
-                        mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
+                        mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED))
                 .isEqualTo(VibrationEffect.createWaveform(new long[] {10, 20, 30, 40}, -1));
@@ -216,25 +211,44 @@
     public void testNoValidCustomizationAndResourcePresentForSafeModeEnabled_noVibrationUsed()
                 throws Exception {
         mockSafeModeEnabledVibration(null);
-        SparseArray<VibrationEffect> customizations = new SparseArray<>();
-        customizations.put(SAFE_MODE_ENABLED, PRIMITIVE_CLICK_EFFECT);
-
-        // Test with a customization that is not supported by the vibrator.
         HapticFeedbackVibrationProvider hapticProvider =
-                new HapticFeedbackVibrationProvider(mResourcesMock, mVibrator, customizations);
-
-        assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)).isNull();
-
-        // Test with no customizations.
-        hapticProvider =
                 new HapticFeedbackVibrationProvider(
-                        mResourcesMock, mVibrator, /* hapticCustomizations= */ null);
+                        mResourcesMock, mVibratorInfo, /* hapticCustomizations= */ null);
 
         assertThat(hapticProvider.getVibrationForHapticFeedback(SAFE_MODE_ENABLED)).isNull();
     }
 
+    @Test
+    public void testVibrationAttribute_forNotBypassingIntensitySettings() {
+        HapticFeedbackVibrationProvider hapticProvider =
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
+
+        VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+                SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ false);
+
+        assertThat(attrs.getFlags() & VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
+                .isEqualTo(0);
+    }
+
+    @Test
+    public void testVibrationAttribute_forByassingIntensitySettings() {
+        HapticFeedbackVibrationProvider hapticProvider =
+                new HapticFeedbackVibrationProvider(mResourcesMock, mVibratorInfo);
+
+        VibrationAttributes attrs = hapticProvider.getVibrationAttributesForHapticFeedback(
+                SAFE_MODE_ENABLED, /* bypassVibrationIntensitySetting= */ true);
+
+        assertThat(attrs.getFlags() & VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
+                .isNotEqualTo(0);
+    }
+
     private void mockVibratorPrimitiveSupport(int... supportedPrimitives) {
-        mVibrator = new FakeVibrator(mContext, supportedPrimitives);
+        VibratorInfo.Builder builder = new VibratorInfo.Builder(/* id= */ 1)
+                .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+        for (int primitive : supportedPrimitives) {
+            builder.setSupportedPrimitive(primitive, 10);
+        }
+        mVibratorInfo = builder.build();
     }
 
     private void mockHapticTextSupport(boolean supported) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 4e3a893..c25f0cb 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -30,6 +31,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -45,6 +47,7 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.pm.PackageManagerInternal;
+import android.content.res.Resources;
 import android.hardware.input.IInputManager;
 import android.hardware.input.InputManager;
 import android.hardware.input.InputManagerGlobal;
@@ -79,8 +82,10 @@
 import android.os.vibrator.VibrationEffectSegment;
 import android.provider.Settings;
 import android.util.ArraySet;
+import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.view.Display;
+import android.view.HapticFeedbackConstants;
 import android.view.InputDevice;
 
 import androidx.test.InstrumentationRegistry;
@@ -169,6 +174,8 @@
 
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
 
+    private SparseArray<VibrationEffect>  mHapticFeedbackVibrationMap = new SparseArray<>();
+
     private VibratorManagerService mService;
     private Context mContextSpy;
     private TestLooper mTestLooper;
@@ -309,6 +316,12 @@
                         mExternalVibratorService =
                                 (VibratorManagerService.ExternalVibratorService) serviceInstance;
                     }
+
+                    HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
+                            Resources resources, VibratorInfo vibratorInfo) {
+                        return new HapticFeedbackVibrationProvider(
+                                resources, vibratorInfo, mHapticFeedbackVibrationMap);
+                    }
                 });
         return mService;
     }
@@ -623,6 +636,18 @@
     }
 
     @Test
+    public void vibrate_withoutVibratePermission_throwsSecurityException() {
+        denyPermission(android.Manifest.permission.VIBRATE);
+        VibratorManagerService service = createSystemReadyService();
+
+        assertThrows("Expected vibrating without permission to fail!",
+                SecurityException.class,
+                () -> vibrate(service,
+                        VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK),
+                        VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH)));
+    }
+
+    @Test
     public void vibrate_withRingtone_usesRingerModeSettings() throws Exception {
         mockVibrators(1);
         FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
@@ -1274,6 +1299,60 @@
     }
 
     @Test
+    public void performHapticFeedback_doesNotRequirePermission() throws Exception {
+        denyPermission(android.Manifest.permission.VIBRATE);
+        mHapticFeedbackVibrationMap.put(
+                HapticFeedbackConstants.KEYBOARD_TAP,
+                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createSystemReadyService();
+
+        HalVibration vibration =
+                performHapticFeedbackAndWaitUntilFinished(
+                        service, HapticFeedbackConstants.KEYBOARD_TAP, /* always= */ true);
+
+        List<VibrationEffectSegment> playedSegments = fakeVibrator.getAllEffectSegments();
+        assertEquals(1, playedSegments.size());
+        PrebakedSegment segment = (PrebakedSegment) playedSegments.get(0);
+        assertEquals(VibrationEffect.EFFECT_CLICK, segment.getEffectId());
+        assertEquals(VibrationAttributes.USAGE_TOUCH, vibration.callerInfo.attrs.getUsage());
+    }
+
+    @Test
+    public void performHapticFeedback_doesNotVibrateWhenVibratorInfoNotReady() throws Exception {
+        denyPermission(android.Manifest.permission.VIBRATE);
+        mHapticFeedbackVibrationMap.put(
+                HapticFeedbackConstants.KEYBOARD_TAP,
+                VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK));
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setVibratorInfoLoadSuccessful(false);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createService();
+
+        performHapticFeedbackAndWaitUntilFinished(
+                service, HapticFeedbackConstants.KEYBOARD_TAP, /* always= */ true);
+
+        assertTrue(fakeVibrator.getAllEffectSegments().isEmpty());
+    }
+
+    @Test
+    public void performHapticFeedback_doesNotVibrateForInvalidConstant() throws Exception {
+        denyPermission(android.Manifest.permission.VIBRATE);
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+
+        // These are bad haptic feedback IDs, so expect no vibration played.
+        performHapticFeedbackAndWaitUntilFinished(service, /* constant= */ -1, /* always= */ false);
+        performHapticFeedbackAndWaitUntilFinished(
+                service, HapticFeedbackConstants.NO_HAPTICS, /* always= */ true);
+
+        assertTrue(mVibratorProviders.get(1).getAllEffectSegments().isEmpty());
+    }
+
+    @Test
     public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception {
         int defaultNotificationIntensity =
                 mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_NOTIFICATION);
@@ -2231,6 +2310,18 @@
                 mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
     }
 
+    private HalVibration performHapticFeedbackAndWaitUntilFinished(VibratorManagerService service,
+                int constant, boolean always) throws InterruptedException {
+        HalVibration vib =
+                service.performHapticFeedbackInternal(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME,
+                        constant, always, "some reason", service);
+        if (vib != null) {
+            vib.waitForEnd();
+        }
+
+        return vib;
+    }
+
     private void vibrateAndWaitUntilFinished(VibratorManagerService service, VibrationEffect effect,
             VibrationAttributes attrs) throws InterruptedException {
         vibrateAndWaitUntilFinished(service, CombinedVibration.createParallel(effect), attrs);
@@ -2239,8 +2330,8 @@
     private void vibrateAndWaitUntilFinished(VibratorManagerService service,
             CombinedVibration effect, VibrationAttributes attrs) throws InterruptedException {
         HalVibration vib =
-                service.vibrateInternal(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME, effect, attrs,
-                        "some reason", service);
+                service.vibrateWithPermissionCheck(UID, Display.DEFAULT_DISPLAY, PACKAGE_NAME,
+                        effect, attrs, "some reason", service);
         if (vib != null) {
             vib.waitForEnd();
         }
@@ -2271,4 +2362,9 @@
         }
         return predicateResult;
     }
+
+    private void denyPermission(String permission) {
+        doThrow(new SecurityException()).when(mContextSpy)
+                .enforceCallingOrSelfPermission(eq(permission), anyString());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
index 8fadecd..e13dc3e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutLoggingTests.java
@@ -65,6 +65,12 @@
                         KeyboardLogEvent.RECENT_APPS, KeyEvent.KEYCODE_TAB, ALT_ON},
                 {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK}, KeyboardLogEvent.BACK,
                         KeyEvent.KEYCODE_BACK, 0},
+                {"Meta + `(grave) -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_GRAVE},
+                        KeyboardLogEvent.BACK, KeyEvent.KEYCODE_GRAVE, META_ON},
+                {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
+                        KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DPAD_LEFT, META_ON},
+                {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL},
+                        KeyboardLogEvent.BACK, KeyEvent.KEYCODE_DEL, META_ON},
                 {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
                         KeyboardLogEvent.APP_SWITCH, KeyEvent.KEYCODE_APP_SWITCH, 0},
                 {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST},
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index dd90e04..bf86563 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
 import static android.view.DisplayCutout.NO_CUTOUT;
 import static android.view.InsetsSource.ID_IME;
 import static android.view.RoundedCorners.NO_ROUNDED_CORNERS;
@@ -427,11 +428,11 @@
     @SetupWindows(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD })
     @Test
     public void testImeMinimalSourceFrame() {
+        Assume.assumeFalse("Behavior no longer needed with ENABLE_HIDE_IME_CAPTION_BAR",
+                ENABLE_HIDE_IME_CAPTION_BAR);
+
         final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
-        final DisplayInfo displayInfo = new DisplayInfo();
-        displayInfo.logicalWidth = 1000;
-        displayInfo.logicalHeight = 2000;
-        displayInfo.rotation = ROTATION_0;
+        final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
 
         WindowManager.LayoutParams attrs = mNavBarWindow.mAttrs;
         displayPolicy.addWindowLw(mNavBarWindow, attrs);
@@ -466,10 +467,6 @@
     @Test
     public void testImeInsetsGivenContentFrame() {
         final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
-        final DisplayInfo displayInfo = new DisplayInfo();
-        displayInfo.logicalWidth = 1000;
-        displayInfo.logicalHeight = 2000;
-        displayInfo.rotation = ROTATION_0;
 
         mDisplayContent.setInputMethodWindowLocked(mImeWindow);
         mImeWindow.getControllableInsetProvider().setServerVisible(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
index 06033c7..3fcec96 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
@@ -184,7 +184,7 @@
                 LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
         firstPersister.setLetterboxPositionForVerticalReachability(false,
                 LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
-        waitForCompletion(mPersisterQueue);
+        waitForCompletion(firstPersisterQueue);
         final int newPositionForHorizontalReachability =
                 firstPersister.getLetterboxPositionForHorizontalReachability(false);
         final int newPositionForVerticalReachability =
diff --git a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java
index bb0d30a..5460e4e87 100644
--- a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java
+++ b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java
@@ -421,7 +421,7 @@
 
     @Test
     public void testChoreographerAttachedAfterSetFrameRate() {
-        Log.i(TAG, "adyabr: starting testChoreographerAttachedAfterSetFrameRate");
+        Log.i(TAG, "starting testChoreographerAttachedAfterSetFrameRate");
 
         class TransactionGenerator implements SurfaceControl.TransactionCommittedListener {
             private SurfaceControl mSc;
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 2ccc0fa..a2ae56e 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -95,6 +95,7 @@
         "flickertestapplib",
         "flickerlib",
         "flickerlib-helpers",
+        "flickerlib-trace_processor_shell",
         "platform-test-annotations",
         "wm-flicker-common-app-helpers",
         "wm-shell-flicker-utils",
diff --git a/tests/FlickerTests/manifests/AndroidManifest.xml b/tests/FlickerTests/manifests/AndroidManifest.xml
index 1a34d9e..6bc7cbe 100644
--- a/tests/FlickerTests/manifests/AndroidManifest.xml
+++ b/tests/FlickerTests/manifests/AndroidManifest.xml
@@ -44,8 +44,12 @@
     <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
     <!-- ActivityOptions.makeCustomTaskAnimation() -->
     <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
-    <!-- Allow the test to write directly to /sdcard/ -->
-    <application android:requestLegacyExternalStorage="true" android:largeHeap="true">
+    <!-- Allow the test to connect to perfetto trace processor -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <!-- Allow the test to write directly to /sdcard/ and connect to trace processor -->
+    <application android:requestLegacyExternalStorage="true"
+                 android:networkSecurityConfig="@xml/network_security_config"
+                 android:largeHeap="true">
         <uses-library android:name="android.test.runner"/>
         <uses-library android:name="androidx.window.extensions" android:required="false"/>
 
diff --git a/tests/FlickerTests/res/xml/network_security_config.xml b/tests/FlickerTests/res/xml/network_security_config.xml
new file mode 100644
index 0000000..4bd9ca0
--- /dev/null
+++ b/tests/FlickerTests/res/xml/network_security_config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<network-security-config>
+    <domain-config cleartextTrafficPermitted="true">
+        <domain includeSubdomains="true">localhost</domain>
+    </domain-config>
+</network-security-config>
diff --git a/tests/SurfaceViewBufferTests/Android.bp b/tests/SurfaceViewBufferTests/Android.bp
index dc75f00..38313f8 100644
--- a/tests/SurfaceViewBufferTests/Android.bp
+++ b/tests/SurfaceViewBufferTests/Android.bp
@@ -23,7 +23,10 @@
 
 android_test {
     name: "SurfaceViewBufferTests",
-    srcs: ["**/*.java","**/*.kt"],
+    srcs: [
+        "**/*.java",
+        "**/*.kt",
+    ],
     manifest: "AndroidManifest.xml",
     test_config: "AndroidTest.xml",
     platform_apis: true,
@@ -41,6 +44,7 @@
         "kotlin-stdlib",
         "kotlinx-coroutines-android",
         "flickerlib",
+        "flickerlib-trace_processor_shell",
         "truth-prebuilt",
         "cts-wm-util",
         "CtsSurfaceValidatorLib",
@@ -60,7 +64,7 @@
         "libandroid",
     ],
     include_dirs: [
-        "system/core/include"
+        "system/core/include",
     ],
     stl: "libc++_static",
     cflags: [
diff --git a/tests/SurfaceViewBufferTests/AndroidManifest.xml b/tests/SurfaceViewBufferTests/AndroidManifest.xml
index 78415e8..798c67a 100644
--- a/tests/SurfaceViewBufferTests/AndroidManifest.xml
+++ b/tests/SurfaceViewBufferTests/AndroidManifest.xml
@@ -29,9 +29,12 @@
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
     <!-- Save failed test bitmap images !-->
     <uses-permission android:name="android.Manifest.permission.WRITE_EXTERNAL_STORAGE"/>
+    <!-- Allow the test to connect to perfetto trace processor -->
+    <uses-permission android:name="android.permission.INTERNET"/>
 
     <application android:allowBackup="false"
-         android:supportsRtl="true">
+         android:supportsRtl="true"
+         android:networkSecurityConfig="@xml/network_security_config">
         <activity android:name=".MainActivity"
                   android:taskAffinity="com.android.test.MainActivity"
                   android:theme="@style/AppTheme"
diff --git a/tests/SurfaceViewBufferTests/res/xml/network_security_config.xml b/tests/SurfaceViewBufferTests/res/xml/network_security_config.xml
new file mode 100644
index 0000000..4bd9ca0
--- /dev/null
+++ b/tests/SurfaceViewBufferTests/res/xml/network_security_config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<network-security-config>
+    <domain-config cleartextTrafficPermitted="true">
+        <domain includeSubdomains="true">localhost</domain>
+    </domain-config>
+</network-security-config>
diff --git a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt
index a38019d..b03b733 100644
--- a/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt
+++ b/tests/SurfaceViewBufferTests/src/com/android/test/SurfaceTracingTestBase.kt
@@ -21,11 +21,9 @@
 import android.graphics.Rect
 import android.util.Log
 import androidx.test.ext.junit.rules.ActivityScenarioRule
-import android.tools.common.flicker.subject.layers.LayerSubject
 import android.tools.common.traces.surfaceflinger.LayersTrace
-import android.tools.device.traces.io.ResultWriter
-import android.tools.device.traces.monitors.surfaceflinger.LayersTraceMonitor
 import android.tools.device.traces.monitors.withSFTracing
+import android.tools.device.traces.monitors.PerfettoTraceMonitor
 import junit.framework.Assert
 import org.junit.After
 import org.junit.Before
@@ -33,6 +31,7 @@
 import java.io.FileOutputStream
 import java.io.IOException
 import java.util.concurrent.CountDownLatch
+import perfetto.protos.PerfettoConfig.SurfaceFlingerLayersConfig
 
 open class SurfaceTracingTestBase(useBlastAdapter: Boolean) :
         SurfaceViewBufferTestBase(useBlastAdapter) {
@@ -43,7 +42,7 @@
     @Before
     override fun setup() {
         super.setup()
-        stopLayerTrace()
+        PerfettoTraceMonitor.stopAllSessions()
         addSurfaceView()
     }
 
@@ -83,10 +82,6 @@
         instrumentation.waitForIdleSync()
     }
 
-    private fun stopLayerTrace() {
-        LayersTraceMonitor().stop(ResultWriter())
-    }
-
     fun checkPixels(bounds: Rect, @ColorInt color: Int) {
         val screenshot = instrumentation.getUiAutomation().takeScreenshot()
         val pixels = IntArray(screenshot.width * screenshot.height)
@@ -106,14 +101,19 @@
                         Log.e("SurfaceViewBufferTests", "Error writing bitmap to file", e)
                     }
                 }
-                Assert.assertEquals("Checking $bounds found mismatch $i,$j",
-                        Color.valueOf(color), Color.valueOf(actualColor))
+                Assert.assertEquals(
+                    "Checking $bounds found mismatch $i,$j",
+                    Color.valueOf(color),
+                    Color.valueOf(actualColor)
+                )
             }
         }
     }
 
     private companion object {
-        private const val TRACE_FLAGS =
-                (1 shl 0) or (1 shl 5) or (1 shl 6) // TRACE_CRITICAL | TRACE_BUFFERS | TRACE_SYNC
+        private val TRACE_FLAGS = listOf(
+            SurfaceFlingerLayersConfig.TraceFlag.TRACE_FLAG_BUFFERS,
+            SurfaceFlingerLayersConfig.TraceFlag.TRACE_FLAG_VIRTUAL_DISPLAYS,
+        )
     }
 }
\ No newline at end of file
diff --git a/tests/TaskOrganizerTest/Android.bp b/tests/TaskOrganizerTest/Android.bp
index 9b72d35..bf12f42 100644
--- a/tests/TaskOrganizerTest/Android.bp
+++ b/tests/TaskOrganizerTest/Android.bp
@@ -25,7 +25,10 @@
 
 android_test {
     name: "TaskOrganizerTest",
-    srcs: ["**/*.java","**/*.kt"],
+    srcs: [
+        "**/*.java",
+        "**/*.kt",
+    ],
     manifest: "AndroidManifest.xml",
     test_config: "AndroidTest.xml",
     platform_apis: true,
@@ -39,6 +42,7 @@
         "kotlin-stdlib",
         "kotlinx-coroutines-android",
         "flickerlib",
+        "flickerlib-trace_processor_shell",
         "truth-prebuilt",
     ],
-}
\ No newline at end of file
+}
diff --git a/tests/TaskOrganizerTest/AndroidManifest.xml b/tests/TaskOrganizerTest/AndroidManifest.xml
index 1f1bd3e..cbeb246 100644
--- a/tests/TaskOrganizerTest/AndroidManifest.xml
+++ b/tests/TaskOrganizerTest/AndroidManifest.xml
@@ -16,9 +16,11 @@
     <uses-permission android:name="android.permission.CHANGE_CONFIGURATION"/>
     <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+    <!-- Allow the test to connect to perfetto trace processor -->
+    <uses-permission android:name="android.permission.INTERNET"/>
     <!-- Enable / Disable tracing !-->
     <uses-permission android:name="android.permission.DUMP" />
-    <application>
+    <application android:networkSecurityConfig="@xml/network_security_config">
       <activity android:name="TaskOrganizerMultiWindowTest"
            android:label="TaskOrganizer MW Test"
            android:exported="true"
diff --git a/tests/TaskOrganizerTest/res/xml/network_security_config.xml b/tests/TaskOrganizerTest/res/xml/network_security_config.xml
new file mode 100644
index 0000000..e450a99
--- /dev/null
+++ b/tests/TaskOrganizerTest/res/xml/network_security_config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+~ 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.
+-->
+<network-security-config>
+    <domain-config cleartextTrafficPermitted="true">
+        <domain includeSubdomains="true">localhost</domain>
+    </domain-config>
+</network-security-config>
\ No newline at end of file
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt b/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt
index 6f4f7b1..2c7905d 100644
--- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/ResizeTasksSyncTest.kt
@@ -22,8 +22,6 @@
 import androidx.test.runner.AndroidJUnit4
 import android.tools.common.datatypes.Size
 import android.tools.common.flicker.subject.layers.LayersTraceSubject
-import android.tools.device.traces.io.ResultWriter
-import android.tools.device.traces.monitors.surfaceflinger.LayersTraceMonitor
 import android.tools.device.traces.monitors.withSFTracing
 import org.junit.After
 import org.junit.Before
@@ -41,16 +39,13 @@
     @get:Rule
     var scenarioRule: ActivityScenarioRule<TaskOrganizerMultiWindowTest> =
             ActivityScenarioRule<TaskOrganizerMultiWindowTest>(
-                    TaskOrganizerMultiWindowTest::class.java)
+                    TaskOrganizerMultiWindowTest::class.java
+            )
 
     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
 
     @Before
     fun setup() {
-        val monitor = LayersTraceMonitor()
-        if (monitor.isEnabled) {
-            monitor.stop(ResultWriter())
-        }
         val firstTaskBounds = Rect(0, 0, 1080, 1000)
         val secondTaskBounds = Rect(0, 1000, 1080, 2000)
 
@@ -71,7 +66,7 @@
         val firstBounds = Rect(0, 0, 1080, 800)
         val secondBounds = Rect(0, 1000, 1080, 1800)
 
-        val trace = withSFTracing(TRACE_FLAGS) {
+        val trace = withSFTracing() {
             lateinit var resizeReadyLatch: CountDownLatch
             scenarioRule.getScenario().onActivity {
                 resizeReadyLatch = it.resizeTaskView(firstBounds, secondBounds)
@@ -106,7 +101,6 @@
     }
 
     companion object {
-        private const val TRACE_FLAGS = 0x1 // TRACE_CRITICAL
         private const val FIRST_ACTIVITY = "Activity1"
         private const val SECOND_ACTIVITY = "Activity2"
     }