Merge "Adding support for nested LayerDrawables in GhostedViewLaunchAnimatorController" into main
diff --git a/Android.bp b/Android.bp
index f6a9328..b139b7e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -132,6 +132,7 @@
         ":libcamera_client_aidl",
         ":libcamera_client_framework_aidl",
         ":libupdate_engine_aidl",
+        ":libupdate_engine_stable-V2-java-source",
         ":logd_aidl",
         ":resourcemanager_aidl",
         ":storaged_aidl",
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 117faa2..d59775f 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -132,6 +132,9 @@
     },
     {
       "name": "vts_treble_vintf_vendor_test"
+    },
+    {
+      "name": "CtsStrictJavaPackagesTestCases"
     }
   ],
   "postsubmit-ravenwood": [
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 28b2d4b..50c9fd3 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -900,6 +900,15 @@
     ],
     api_levels_sdk_type: "system",
     extensions_info_file: ":sdk-extensions-info",
+    dists: [
+        // Make the api-versions.xml file for the system API available in the
+        // sdk build target.
+        {
+            targets: ["sdk"],
+            dest: "api-versions_system.xml",
+            tag: ".api_versions.xml",
+        },
+    ],
 }
 
 // This module can be built with:
diff --git a/core/api/current.txt b/core/api/current.txt
index 5abb92b..c7b921c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -22102,7 +22102,7 @@
   }
 
   @FlaggedApi("android.media.audio.loudness_configurator_api") public class LoudnessCodecConfigurator {
-    method @FlaggedApi("android.media.audio.loudness_configurator_api") public void addMediaCodec(@NonNull android.media.MediaCodec);
+    method @FlaggedApi("android.media.audio.loudness_configurator_api") public boolean addMediaCodec(@NonNull android.media.MediaCodec);
     method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecConfigurator create();
     method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public static android.media.LoudnessCodecConfigurator create(@NonNull java.util.concurrent.Executor, @NonNull android.media.LoudnessCodecConfigurator.OnLoudnessCodecUpdateListener);
     method @FlaggedApi("android.media.audio.loudness_configurator_api") @NonNull public android.os.Bundle getLoudnessCodecParams(@NonNull android.media.AudioTrack, @NonNull android.media.MediaCodec);
@@ -28825,6 +28825,8 @@
     method public boolean isSecureNfcEnabled();
     method public boolean isSecureNfcSupported();
     method @FlaggedApi("android.nfc.enable_nfc_charging") public boolean isWlcEnabled();
+    method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void resetDiscoveryTechnology(@NonNull android.app.Activity);
+    method @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public void setDiscoveryTechnology(@NonNull android.app.Activity, int, int);
     field public static final String ACTION_ADAPTER_STATE_CHANGED = "android.nfc.action.ADAPTER_STATE_CHANGED";
     field public static final String ACTION_NDEF_DISCOVERED = "android.nfc.action.NDEF_DISCOVERED";
     field @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static final String ACTION_PREFERRED_PAYMENT_CHANGED = "android.nfc.action.PREFERRED_PAYMENT_CHANGED";
@@ -28840,6 +28842,13 @@
     field public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
     field public static final String EXTRA_SECURE_ELEMENT_NAME = "android.nfc.extra.SECURE_ELEMENT_NAME";
     field public static final String EXTRA_TAG = "android.nfc.extra.TAG";
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_DISABLE = 0; // 0x0
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_KEEP = -1; // 0xffffffff
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_A = 1; // 0x1
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_B = 2; // 0x2
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_LISTEN_NFC_PASSIVE_F = 4; // 0x4
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_DISABLE = 0; // 0x0
+    field @FlaggedApi("android.nfc.enable_nfc_set_discovery_tech") public static final int FLAG_READER_KEEP = -1; // 0xffffffff
     field public static final int FLAG_READER_NFC_A = 1; // 0x1
     field public static final int FLAG_READER_NFC_B = 2; // 0x2
     field public static final int FLAG_READER_NFC_BARCODE = 16; // 0x10
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 0d4169f..9077d02 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -95,7 +95,7 @@
     field public static final String BYPASS_ROLE_QUALIFICATION = "android.permission.BYPASS_ROLE_QUALIFICATION";
     field public static final String CALL_AUDIO_INTERCEPTION = "android.permission.CALL_AUDIO_INTERCEPTION";
     field public static final String CAMERA_DISABLE_TRANSMIT_LED = "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
-    field public static final String CAMERA_HEADLESS_SYSTEM_USER = "android.permission.CAMERA_HEADLESS_SYSTEM_USER";
+    field @FlaggedApi("com.android.internal.camera.flags.camera_hsum_permission") public static final String CAMERA_HEADLESS_SYSTEM_USER = "android.permission.CAMERA_HEADLESS_SYSTEM_USER";
     field public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission.CAMERA_OPEN_CLOSE_LISTENER";
     field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";
     field public static final String CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = "android.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD";
@@ -473,7 +473,7 @@
     field public static final int config_defaultCallScreening = 17039398; // 0x1040026
     field public static final int config_defaultDialer = 17039395; // 0x1040023
     field public static final int config_defaultNotes = 17039429; // 0x1040045
-    field public static final int config_defaultRetailDemo;
+    field @FlaggedApi("android.permission.flags.retail_demo_role_enabled") public static final int config_defaultRetailDemo;
     field public static final int config_defaultSms = 17039396; // 0x1040024
     field public static final int config_devicePolicyManagement = 17039421; // 0x104003d
     field public static final int config_feedbackIntentExtraKey = 17039391; // 0x104001f
@@ -4141,7 +4141,7 @@
     field public static final int PROTECTION_FLAG_MODULE = 4194304; // 0x400000
     field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
     field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000
-    field @Deprecated public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
+    field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
     field public static final int PROTECTION_FLAG_ROLE = 67108864; // 0x4000000
     field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
     field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
@@ -10571,7 +10571,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static int rebootAndApply(@NonNull android.content.Context, @NonNull String, boolean) throws java.io.IOException;
     method @RequiresPermission(allOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static void rebootWipeAb(android.content.Context, java.io.File, String) throws java.io.IOException;
     method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void scheduleUpdateOnBoot(android.content.Context, java.io.File) throws java.io.IOException;
-    method public static boolean verifyPackageCompatibility(java.io.File) throws java.io.IOException;
+    method @Deprecated public static boolean verifyPackageCompatibility(java.io.File) throws java.io.IOException;
     field public static final int RESUME_ON_REBOOT_REBOOT_ERROR_INVALID_PACKAGE_NAME = 2000; // 0x7d0
     field public static final int RESUME_ON_REBOOT_REBOOT_ERROR_LSKF_NOT_CAPTURED = 3000; // 0xbb8
     field public static final int RESUME_ON_REBOOT_REBOOT_ERROR_PROVIDER_PREPARATION_FAILURE = 5000; // 0x1388
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 5d4d5e2..f9583d2 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -24,7 +24,9 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.inMultiWindowMode;
 import static android.os.Process.myUid;
+
 import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
+
 import static java.lang.Character.MIN_VALUE;
 
 import android.annotation.AnimRes;
@@ -45,6 +47,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UiContext;
+import android.app.ActivityOptions.SceneTransitionInfo;
 import android.app.VoiceInteractor.Request;
 import android.app.admin.DevicePolicyManager;
 import android.app.assist.AssistContent;
@@ -930,8 +933,8 @@
     @UnsupportedAppUsage
     final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
 
-    /** The options for scene transition. */
-    ActivityOptions mPendingOptions;
+    /** The scene transition info. */
+    SceneTransitionInfo mSceneTransitionInfo;
 
     /** Whether this activity was launched from a bubble. **/
     boolean mLaunchedFromBubble;
@@ -5807,10 +5810,9 @@
 
     private Bundle transferSpringboardActivityOptions(@Nullable Bundle options) {
         if (options == null && (mWindow != null && !mWindow.isActive())) {
-            final ActivityOptions activityOptions = getActivityOptions();
-            if (activityOptions != null &&
-                    activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
-                return activityOptions.toBundle();
+            final SceneTransitionInfo info = getSceneTransitionInfo();
+            if (info != null) {
+                return ActivityOptions.makeBasic().setSceneTransitionInfo(info).toBundle();
             }
         }
         return options;
@@ -8079,8 +8081,10 @@
      *
      * @param callback the method to call when all visible activities behind this one have been
      * drawn and it is safe to make this activity translucent again.
-     * @param options activity options delivered to the activity below this one. The options
-     * are retrieved using {@link #getActivityOptions}.
+     * @param options activity options that created from
+     *             {@link ActivityOptions#makeSceneTransitionAnimation} which will be converted to
+     *             {@link SceneTransitionInfo} and delivered to the activity below this one. The
+     *              options are retrieved using {@link #getSceneTransitionInfo}.
      * @return <code>true</code> if Window was opaque and will become translucent or
      * <code>false</code> if window was translucent and no change needed to be made.
      *
@@ -8116,27 +8120,27 @@
     }
 
     /** @hide */
-    public void onNewActivityOptions(ActivityOptions options) {
-        mActivityTransitionState.setEnterActivityOptions(this, options);
+    public void onNewSceneTransitionInfo(ActivityOptions.SceneTransitionInfo info) {
+        mActivityTransitionState.setEnterSceneTransitionInfo(this, info);
         if (!mStopped) {
             mActivityTransitionState.enterReady(this);
         }
     }
 
     /**
-     * Takes the ActivityOptions passed in from the launching activity or passed back
+     * Takes the {@link SceneTransitionInfo} passed in from the launching activity or passed back
      * from an activity launched by this activity in its call to {@link
      * #convertToTranslucent(TranslucentConversionListener, ActivityOptions)}
      *
-     * @return The ActivityOptions passed to {@link #convertToTranslucent}.
+     * @return The {@link SceneTransitionInfo} which based on the ActivityOptions that originally
+     *         passed to {@link #convertToTranslucent}.
      * @hide
      */
-    @UnsupportedAppUsage
-    ActivityOptions getActivityOptions() {
-        final ActivityOptions options = mPendingOptions;
-        // The option only applies once.
-        mPendingOptions = null;
-        return options;
+    SceneTransitionInfo getSceneTransitionInfo() {
+        final SceneTransitionInfo sceneTransitionInfo = mSceneTransitionInfo;
+        // The info only applies once.
+        mSceneTransitionInfo = null;
+        return sceneTransitionInfo;
     }
 
     /**
@@ -8780,7 +8784,7 @@
         mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
                 com.android.internal.R.styleable.Window_windowNoDisplay, false);
         mFragments.dispatchActivityCreated();
-        mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
+        mActivityTransitionState.setEnterSceneTransitionInfo(this, getSceneTransitionInfo());
         dispatchActivityPostCreated(icicle);
         Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
@@ -8798,7 +8802,7 @@
                     + mComponent.getClassName());
         }
         dispatchActivityPreStarted();
-        mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
+        mActivityTransitionState.setEnterSceneTransitionInfo(this, getSceneTransitionInfo());
         mFragments.noteStateNotSaved();
         mCalled = false;
         mFragments.execPendingActions();
diff --git a/core/java/android/app/ActivityOptions.aidl b/core/java/android/app/ActivityOptions.aidl
new file mode 100644
index 0000000..bd5cd88
--- /dev/null
+++ b/core/java/android/app/ActivityOptions.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+/** @hide */
+parcelable ActivityOptions.SceneTransitionInfo;
\ No newline at end of file
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 9c279c3..8af7ed1 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -357,22 +357,7 @@
     private static final String KEY_APPLY_NO_USER_ACTION_FLAG_FOR_SHORTCUT =
             "android:activity.applyNoUserActionFlagForShortcut";
 
-    /**
-     * For Activity transitions, the calling Activity's TransitionListener used to
-     * notify the called Activity when the shared element and the exit transitions
-     * complete.
-     */
-    private static final String KEY_TRANSITION_COMPLETE_LISTENER
-            = "android:activity.transitionCompleteListener";
-
-    private static final String KEY_TRANSITION_IS_RETURNING
-            = "android:activity.transitionIsReturning";
-    private static final String KEY_TRANSITION_SHARED_ELEMENTS
-            = "android:activity.sharedElementNames";
-    private static final String KEY_RESULT_DATA = "android:activity.resultData";
-    private static final String KEY_RESULT_CODE = "android:activity.resultCode";
-    private static final String KEY_EXIT_COORDINATOR_INDEX
-            = "android:activity.exitCoordinatorIndex";
+    private static final String KEY_SCENE_TRANSITION_INFO = "android:activity.sceneTransitionInfo";
 
     /** See {@link SourceInfo}. */
     private static final String KEY_SOURCE_INFO = "android.activity.sourceInfo";
@@ -472,12 +457,7 @@
     private int mHeight;
     private IRemoteCallback mAnimationStartedListener;
     private IRemoteCallback mAnimationFinishedListener;
-    private ResultReceiver mTransitionReceiver;
-    private boolean mIsReturning;
-    private ArrayList<String> mSharedElementNames;
-    private Intent mResultData;
-    private int mResultCode;
-    private int mExitCoordinatorIndex;
+    private SceneTransitionInfo mSceneTransitionInfo;
     private PendingIntent mUsageTimeReport;
     private int mLaunchDisplayId = INVALID_DISPLAY;
     private int mCallerDisplayId = INVALID_DISPLAY;
@@ -1006,8 +986,11 @@
         ExitTransitionCoordinator exit = makeSceneTransitionAnimation(
                 new ActivityExitTransitionCallbacks(activity), activity.mExitTransitionListener,
                 activity.getWindow(), opts, sharedElements);
-        opts.mExitCoordinatorIndex =
-                activity.mActivityTransitionState.addExitTransitionCoordinator(exit);
+        final SceneTransitionInfo info = opts.getSceneTransitionInfo();
+        if (info != null) {
+            info.setExitCoordinatorKey(
+                    activity.mActivityTransitionState.addExitTransitionCoordinator(exit));
+        }
         return opts;
     }
 
@@ -1029,13 +1012,16 @@
         ActivityOptions opts = new ActivityOptions();
         ExitTransitionCoordinator exit = makeSceneTransitionAnimation(
                 exitCallbacks, callback, window, opts, sharedElements);
-        opts.mExitCoordinatorIndex = -1;
+        final SceneTransitionInfo info = opts.getSceneTransitionInfo();
+        if (info != null) {
+            info.setExitCoordinatorKey(-1);
+        }
         return Pair.create(opts, exit);
     }
 
     /**
-     * This method should be called when the
-     * {@link #startSharedElementAnimation(Window, ExitTransitionCallbacks, Pair[])}
+     * This method should be called when the {@link #startSharedElementAnimation(Window,
+     * ExitTransitionCallbacks, SharedElementCallback, Pair[])}
      * animation must be stopped and the Views reset. This can happen if there was an error
      * from startActivity or a springboard activity and the animation should stop and reset.
      *
@@ -1088,9 +1074,11 @@
 
         ExitTransitionCoordinator exit = new ExitTransitionCoordinator(exitCallbacks, window,
                 callback, names, names, views, false);
-        opts.mTransitionReceiver = exit;
-        opts.mSharedElementNames = names;
-        opts.mIsReturning = false;
+        final SceneTransitionInfo info = new SceneTransitionInfo();
+        info.setResultReceiver(exit);
+        info.setSharedElementNames(names);
+        info.setReturning(false);
+        opts.setSceneTransitionInfo(info);
         return exit;
     }
 
@@ -1111,17 +1099,20 @@
             int resultCode, Intent resultData) {
         ActivityOptions opts = new ActivityOptions();
         opts.mAnimationType = ANIM_SCENE_TRANSITION;
-        opts.mSharedElementNames = sharedElementNames;
-        opts.mTransitionReceiver = exitCoordinator;
-        opts.mIsReturning = true;
-        opts.mResultCode = resultCode;
-        opts.mResultData = resultData;
+        final SceneTransitionInfo info = new SceneTransitionInfo();
+        info.setSharedElementNames(sharedElementNames);
+        info.setResultReceiver(exitCoordinator);
+        info.setReturning(true);
+        info.setResultCode(resultCode);
+        info.setResultData(resultData);
         if (activity == null) {
-            opts.mExitCoordinatorIndex = -1;
+            info.setExitCoordinatorKey(-1);
         } else {
-            opts.mExitCoordinatorIndex =
-                    activity.mActivityTransitionState.addExitTransitionCoordinator(exitCoordinator);
+            info.setExitCoordinatorKey(
+                    activity.mActivityTransitionState.addExitTransitionCoordinator(
+                            exitCoordinator));
         }
+        opts.setSceneTransitionInfo(info);
         return opts;
     }
 
@@ -1269,12 +1260,8 @@
                 break;
 
             case ANIM_SCENE_TRANSITION:
-                mTransitionReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER, android.os.ResultReceiver.class);
-                mIsReturning = opts.getBoolean(KEY_TRANSITION_IS_RETURNING, false);
-                mSharedElementNames = opts.getStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS);
-                mResultData = opts.getParcelable(KEY_RESULT_DATA, android.content.Intent.class);
-                mResultCode = opts.getInt(KEY_RESULT_CODE);
-                mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX);
+                mSceneTransitionInfo = opts.getParcelable(KEY_SCENE_TRANSITION_INFO,
+                        SceneTransitionInfo.class);
                 break;
         }
         mLockTaskMode = opts.getBoolean(KEY_LOCK_TASK_MODE, false);
@@ -1437,9 +1424,6 @@
     }
 
     /** @hide */
-    public int getExitCoordinatorKey() { return mExitCoordinatorIndex; }
-
-    /** @hide */
     public void abort() {
         if (mAnimationStartedListener != null) {
             try {
@@ -1450,35 +1434,17 @@
     }
 
     /** @hide */
-    public boolean isReturning() {
-        return mIsReturning;
-    }
-
-    /**
-     * Returns whether or not the ActivityOptions was created with
-     * {@link #startSharedElementAnimation(Window, Pair[])}.
-     *
-     * @hide
-     */
-    boolean isCrossTask() {
-        return mExitCoordinatorIndex < 0;
+    public ActivityOptions setSceneTransitionInfo(SceneTransitionInfo info) {
+        mSceneTransitionInfo = info;
+        return this;
     }
 
     /** @hide */
-    public ArrayList<String> getSharedElementNames() {
-        return mSharedElementNames;
+    public SceneTransitionInfo getSceneTransitionInfo() {
+        return mSceneTransitionInfo;
     }
 
     /** @hide */
-    public ResultReceiver getResultReceiver() { return mTransitionReceiver; }
-
-    /** @hide */
-    public int getResultCode() { return mResultCode; }
-
-    /** @hide */
-    public Intent getResultData() { return mResultData; }
-
-    /** @hide */
     public PendingIntent getUsageTimeReport() {
         return mUsageTimeReport;
     }
@@ -2102,12 +2068,7 @@
             mPackageName = otherOptions.mPackageName;
         }
         mUsageTimeReport = otherOptions.mUsageTimeReport;
-        mTransitionReceiver = null;
-        mSharedElementNames = null;
-        mIsReturning = false;
-        mResultData = null;
-        mResultCode = 0;
-        mExitCoordinatorIndex = 0;
+        mSceneTransitionInfo = null;
         mAnimationType = otherOptions.mAnimationType;
         switch (otherOptions.mAnimationType) {
             case ANIM_CUSTOM:
@@ -2157,14 +2118,9 @@
                 mAnimationStartedListener = otherOptions.mAnimationStartedListener;
                 break;
             case ANIM_SCENE_TRANSITION:
-                mTransitionReceiver = otherOptions.mTransitionReceiver;
-                mSharedElementNames = otherOptions.mSharedElementNames;
-                mIsReturning = otherOptions.mIsReturning;
+                mSceneTransitionInfo = otherOptions.mSceneTransitionInfo;
                 mThumbnail = null;
                 mAnimationStartedListener = null;
-                mResultData = otherOptions.mResultData;
-                mResultCode = otherOptions.mResultCode;
-                mExitCoordinatorIndex = otherOptions.mExitCoordinatorIndex;
                 break;
         }
         mLockTaskMode = otherOptions.mLockTaskMode;
@@ -2240,14 +2196,9 @@
                         != null ? mAnimationStartedListener.asBinder() : null);
                 break;
             case ANIM_SCENE_TRANSITION:
-                if (mTransitionReceiver != null) {
-                    b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionReceiver);
+                if (mSceneTransitionInfo != null) {
+                    b.putParcelable(KEY_SCENE_TRANSITION_INFO, mSceneTransitionInfo);
                 }
-                b.putBoolean(KEY_TRANSITION_IS_RETURNING, mIsReturning);
-                b.putStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS, mSharedElementNames);
-                b.putParcelable(KEY_RESULT_DATA, mResultData);
-                b.putInt(KEY_RESULT_CODE, mResultCode);
-                b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex);
                 break;
         }
         if (mLockTaskMode) {
@@ -2607,4 +2558,124 @@
             }
         };
     }
+
+    /**
+     * This class contains necessary information for Activity Scene Transition.
+     *
+     * @hide
+     */
+    public static class SceneTransitionInfo implements Parcelable {
+        private boolean mIsReturning;
+        private int mResultCode;
+        @Nullable
+        private Intent mResultData;
+        @Nullable
+        private ArrayList<String> mSharedElementNames;
+        @Nullable
+        private ResultReceiver mResultReceiver;
+        private int mExitCoordinatorIndex;
+
+        public SceneTransitionInfo() {
+        }
+
+        SceneTransitionInfo(Parcel in) {
+            mIsReturning = in.readBoolean();
+            mResultCode = in.readInt();
+            mResultData = in.readTypedObject(Intent.CREATOR);
+            mSharedElementNames = in.createStringArrayList();
+            mResultReceiver = in.readTypedObject(ResultReceiver.CREATOR);
+            mExitCoordinatorIndex = in.readInt();
+        }
+
+        public static final Creator<SceneTransitionInfo> CREATOR = new Creator<>() {
+            @Override
+            public SceneTransitionInfo createFromParcel(Parcel in) {
+                return new SceneTransitionInfo(in);
+            }
+
+            @Override
+            public SceneTransitionInfo[] newArray(int size) {
+                return new SceneTransitionInfo[size];
+            }
+        };
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeBoolean(mIsReturning);
+            dest.writeInt(mResultCode);
+            dest.writeTypedObject(mResultData, flags);
+            dest.writeStringList(mSharedElementNames);
+            dest.writeTypedObject(mResultReceiver, flags);
+            dest.writeInt(mExitCoordinatorIndex);
+        }
+
+        public void setReturning(boolean isReturning) {
+            mIsReturning = isReturning;
+        }
+
+        public boolean isReturning() {
+            return mIsReturning;
+        }
+
+        public void setResultCode(int resultCode) {
+            mResultCode = resultCode;
+        }
+
+        public int getResultCode() {
+            return mResultCode;
+        }
+
+        public void setResultData(Intent resultData) {
+            mResultData = resultData;
+        }
+
+        @Nullable
+        public Intent getResultData() {
+            return mResultData;
+        }
+
+        public void setSharedElementNames(ArrayList<String> sharedElementNames) {
+            mSharedElementNames = sharedElementNames;
+        }
+
+        @Nullable
+        public ArrayList<String> getSharedElementNames() {
+            return mSharedElementNames;
+        }
+
+        public void setResultReceiver(ResultReceiver resultReceiver) {
+            mResultReceiver = resultReceiver;
+        }
+
+        @Nullable
+        public ResultReceiver getResultReceiver() {
+            return mResultReceiver;
+        }
+
+        public void setExitCoordinatorKey(int exitCoordinatorKey) {
+            mExitCoordinatorIndex = exitCoordinatorKey;
+        }
+
+        public int getExitCoordinatorKey() {
+            return mExitCoordinatorIndex;
+        }
+
+        boolean isCrossTask() {
+            return mExitCoordinatorIndex < 0;
+        }
+
+        @Override
+        public String toString() {
+            return "SceneTransitionInfo, mIsReturning=" + mIsReturning
+                    + ", mResultCode=" + mResultCode + ", mResultData=" + mResultData
+                    + ", mSharedElementNames=" + mSharedElementNames
+                    + ", mTransitionReceiver=" + mResultReceiver
+                    + ", mExitCoordinatorIndex=" + mExitCoordinatorIndex;
+        }
+    }
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7e5326e..949e2ba 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -43,6 +43,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityOptions.SceneTransitionInfo;
 import android.app.RemoteServiceException.BadForegroundServiceNotificationException;
 import android.app.RemoteServiceException.BadUserInitiatedJobNotificationException;
 import android.app.RemoteServiceException.CannotPostForegroundServiceNotificationException;
@@ -632,8 +633,8 @@
         @UnsupportedAppUsage
         boolean mPreserveWindow;
 
-        /** The options for scene transition. */
-        ActivityOptions mActivityOptions;
+        /** The scene transition info. */
+        SceneTransitionInfo mSceneTransitionInfo;
 
         /** Whether this activiy was launched from a bubble. */
         boolean mLaunchedFromBubble;
@@ -660,7 +661,7 @@
                 ActivityInfo info, Configuration overrideConfig,
                 String referrer, IVoiceInteractor voiceInteractor, Bundle state,
                 PersistableBundle persistentState, List<ResultInfo> pendingResults,
-                List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
+                List<ReferrerIntent> pendingNewIntents, SceneTransitionInfo sceneTransitionInfo,
                 boolean isForward, ProfilerInfo profilerInfo, ClientTransactionHandler client,
                 IBinder assistToken, IBinder shareableActivityToken, boolean launchedFromBubble,
                 IBinder taskFragmentToken) {
@@ -680,7 +681,7 @@
             this.profilerInfo = profilerInfo;
             this.overrideConfig = overrideConfig;
             this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo);
-            mActivityOptions = activityOptions;
+            mSceneTransitionInfo = sceneTransitionInfo;
             mLaunchedFromBubble = launchedFromBubble;
             mTaskFragmentToken = taskFragmentToken;
             init();
@@ -1960,9 +1961,9 @@
             sendMessage(H.TRANSLUCENT_CONVERSION_COMPLETE, token, drawComplete ? 1 : 0);
         }
 
-        public void scheduleOnNewActivityOptions(IBinder token, Bundle options) {
-            sendMessage(H.ON_NEW_ACTIVITY_OPTIONS,
-                    new Pair<IBinder, ActivityOptions>(token, ActivityOptions.fromBundle(options)));
+        public void scheduleOnNewSceneTransitionInfo(IBinder token, SceneTransitionInfo info) {
+            sendMessage(H.ON_NEW_SCENE_TRANSITION_INFO,
+                    new Pair<IBinder, SceneTransitionInfo>(token, info));
         }
 
         public void setProcessState(int state) {
@@ -2258,7 +2259,7 @@
         public static final int TRANSLUCENT_CONVERSION_COMPLETE = 144;
         @UnsupportedAppUsage
         public static final int INSTALL_PROVIDER        = 145;
-        public static final int ON_NEW_ACTIVITY_OPTIONS = 146;
+        public static final int ON_NEW_SCENE_TRANSITION_INFO = 146;
         @UnsupportedAppUsage
         public static final int ENTER_ANIMATION_COMPLETE = 149;
         public static final int START_BINDER_TRACKING = 150;
@@ -2314,7 +2315,7 @@
                     case REQUEST_ASSIST_CONTEXT_EXTRAS: return "REQUEST_ASSIST_CONTEXT_EXTRAS";
                     case TRANSLUCENT_CONVERSION_COMPLETE: return "TRANSLUCENT_CONVERSION_COMPLETE";
                     case INSTALL_PROVIDER: return "INSTALL_PROVIDER";
-                    case ON_NEW_ACTIVITY_OPTIONS: return "ON_NEW_ACTIVITY_OPTIONS";
+                    case ON_NEW_SCENE_TRANSITION_INFO: return "ON_NEW_SCENE_TRANSITION_INFO";
                     case ENTER_ANIMATION_COMPLETE: return "ENTER_ANIMATION_COMPLETE";
                     case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
                     case ATTACH_AGENT: return "ATTACH_AGENT";
@@ -2520,9 +2521,10 @@
                         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                     }
                     break;
-                case ON_NEW_ACTIVITY_OPTIONS:
-                    Pair<IBinder, ActivityOptions> pair = (Pair<IBinder, ActivityOptions>) msg.obj;
-                    onNewActivityOptions(pair.first, pair.second);
+                case ON_NEW_SCENE_TRANSITION_INFO:
+                    Pair<IBinder, SceneTransitionInfo> pair =
+                            (Pair<IBinder, SceneTransitionInfo>) msg.obj;
+                    onNewSceneTransitionInfo(pair.first, pair.second);
                     break;
                 case ENTER_ANIMATION_COMPLETE:
                     handleEnterAnimationComplete((IBinder) msg.obj);
@@ -3921,9 +3923,9 @@
                     activity.setTheme(theme);
                 }
 
-                if (r.mActivityOptions != null) {
-                    activity.mPendingOptions = r.mActivityOptions;
-                    r.mActivityOptions = null;
+                if (r.mSceneTransitionInfo != null) {
+                    activity.mSceneTransitionInfo = r.mSceneTransitionInfo;
+                    r.mSceneTransitionInfo = null;
                 }
                 activity.mLaunchedFromBubble = r.mLaunchedFromBubble;
                 activity.mCalled = false;
@@ -3962,7 +3964,7 @@
 
     @Override
     public void handleStartActivity(ActivityClientRecord r,
-            PendingTransactionActions pendingActions, ActivityOptions activityOptions) {
+            PendingTransactionActions pendingActions, SceneTransitionInfo sceneTransitionInfo) {
         final Activity activity = r.activity;
         if (!r.stopped) {
             throw new IllegalStateException("Can't start activity that is not stopped.");
@@ -3973,8 +3975,8 @@
         }
 
         unscheduleGcIdler();
-        if (activityOptions != null) {
-            activity.mPendingOptions = activityOptions;
+        if (sceneTransitionInfo != null) {
+            activity.mSceneTransitionInfo = sceneTransitionInfo;
         }
 
         // Start
@@ -4349,10 +4351,10 @@
         }
     }
 
-    public void onNewActivityOptions(IBinder token, ActivityOptions options) {
+    public void onNewSceneTransitionInfo(IBinder token, SceneTransitionInfo info) {
         ActivityClientRecord r = mActivities.get(token);
         if (r != null) {
-            r.activity.onNewActivityOptions(options);
+            r.activity.onNewSceneTransitionInfo(info);
         }
     }
 
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index 6f4bb45..d947a9b 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -15,6 +15,7 @@
  */
 package android.app;
 
+import android.app.ActivityOptions.SceneTransitionInfo;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.ResultReceiver;
@@ -81,9 +82,9 @@
     private EnterTransitionCoordinator mEnterTransitionCoordinator;
 
     /**
-     * ActivityOptions used on entering this Activity.
+     * {@link SceneTransitionInfo} used on entering this Activity.
      */
-    private ActivityOptions mEnterActivityOptions;
+    private SceneTransitionInfo mEnterSceneTransitionInfo;
 
     /**
      * Has an exit transition been started? If so, we don't want to double-exit.
@@ -165,7 +166,7 @@
         }
     }
 
-    public void setEnterActivityOptions(Activity activity, ActivityOptions options) {
+    public void setEnterSceneTransitionInfo(Activity activity, SceneTransitionInfo info) {
         final Window window = activity.getWindow();
         if (window == null) {
             return;
@@ -173,16 +174,15 @@
         // ensure Decor View has been created so that the window features are activated
         window.getDecorView();
         if (window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
-                && options != null && mEnterActivityOptions == null
-                && mEnterTransitionCoordinator == null
-                && options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
-            mEnterActivityOptions = options;
+                && info != null && mEnterSceneTransitionInfo == null
+                && mEnterTransitionCoordinator == null) {
+            mEnterSceneTransitionInfo = info;
             mIsEnterTriggered = false;
-            if (mEnterActivityOptions.isReturning()) {
+            if (mEnterSceneTransitionInfo.isReturning()) {
                 restoreExitedViews();
-                int result = mEnterActivityOptions.getResultCode();
+                int result = mEnterSceneTransitionInfo.getResultCode();
                 if (result != 0) {
-                    Intent intent = mEnterActivityOptions.getResultData();
+                    Intent intent = mEnterSceneTransitionInfo.getResultData();
                     if (intent != null) {
                         intent.setExtrasClassLoader(activity.getClassLoader());
                     }
@@ -193,25 +193,26 @@
     }
 
     public void enterReady(Activity activity) {
-        if (mEnterActivityOptions == null || mIsEnterTriggered) {
+        if (mEnterSceneTransitionInfo == null || mIsEnterTriggered) {
             return;
         }
         mIsEnterTriggered = true;
         mHasExited = false;
-        ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
-        ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
-        final boolean isReturning = mEnterActivityOptions.isReturning();
+        final ArrayList<String> sharedElementNames =
+                mEnterSceneTransitionInfo.getSharedElementNames();
+        ResultReceiver resultReceiver = mEnterSceneTransitionInfo.getResultReceiver();
+        final boolean isReturning = mEnterSceneTransitionInfo.isReturning();
         if (isReturning) {
             restoreExitedViews();
             activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
         }
         getPendingExitNames(); // Set mPendingExitNames before resetting mEnterTransitionCoordinator
         mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
-                resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
-                mEnterActivityOptions.isCrossTask());
-        if (mEnterActivityOptions.isCrossTask()) {
-            mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
-            mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
+                resultReceiver, sharedElementNames, mEnterSceneTransitionInfo.isReturning(),
+                mEnterSceneTransitionInfo.isCrossTask());
+        if (mEnterSceneTransitionInfo.isCrossTask() && sharedElementNames != null) {
+            mExitingFrom = new ArrayList<>(sharedElementNames);
+            mExitingTo = new ArrayList<>(sharedElementNames);
         }
 
         if (!mIsEnterPostponed) {
@@ -248,7 +249,7 @@
         mExitingFrom = null;
         mExitingTo = null;
         mExitingToView = null;
-        mEnterActivityOptions = null;
+        mEnterSceneTransitionInfo = null;
     }
 
     public void onStop(Activity activity) {
@@ -296,7 +297,7 @@
         mExitingToView = null;
         mCalledExitCoordinator = null;
         mEnterTransitionCoordinator = null;
-        mEnterActivityOptions = null;
+        mEnterSceneTransitionInfo = null;
         mExitTransitionCoordinators = null;
     }
 
@@ -386,9 +387,10 @@
                 mExitTransitionCoordinators == null) {
             return;
         }
-        ActivityOptions activityOptions = new ActivityOptions(options);
-        if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
-            int key = activityOptions.getExitCoordinatorKey();
+        final ActivityOptions activityOptions = new ActivityOptions(options);
+        final SceneTransitionInfo info = activityOptions.getSceneTransitionInfo();
+        if (info != null) {
+            int key = info.getExitCoordinatorKey();
             int index = mExitTransitionCoordinators.indexOfKey(key);
             if (index >= 0) {
                 mCalledExitCoordinator = mExitTransitionCoordinators.valueAt(index).get();
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index 25075e9..b300674 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityOptions.SceneTransitionInfo;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.DestroyActivityItem;
@@ -207,7 +208,7 @@
 
     /** Perform activity start. */
     public abstract void handleStartActivity(@NonNull ActivityClientRecord r,
-            PendingTransactionActions pendingActions, ActivityOptions activityOptions);
+            PendingTransactionActions pendingActions, SceneTransitionInfo sceneTransitionInfo);
 
     /** Get package info. */
     public abstract LoadedApk getPackageInfoNoCheck(ApplicationInfo ai);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 014ddd41..edeec77 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -3481,24 +3481,13 @@
         }
         mResources = r;
 
-        // only do this if the user already has more than one preferred locale
-        if (android.content.res.Flags.defaultLocale()
-                && r.getConfiguration().getLocales().size() > 1) {
-            LocaleConfig lc;
-            if (getUserId() < 0) {
-                lc = LocaleConfig.fromContextIgnoringOverride(this);
-            } else {
-                // This is needed because the app might have locale config overrides that need to
-                // be read from disk in order for resources to correctly choose which values to
-                // load.
-                StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads();
-                try {
-                    lc = new LocaleConfig(this);
-                } finally {
-                    StrictMode.setThreadPolicy(policy);
-                }
+        if (r != null) {
+            // only do this if the user already has more than one preferred locale
+            if (android.content.res.Flags.defaultLocale()
+                    && r.getConfiguration().getLocales().size() > 1) {
+                LocaleConfig lc = LocaleConfig.fromContextIgnoringOverride(this);
+                mResourcesManager.setLocaleConfig(lc);
             }
-            mResourcesManager.setLocaleConfig(lc);
         }
     }
 
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index a301c18..59e0e99 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import android.app.ActivityOptions.SceneTransitionInfo;
 import android.app.ContentProviderHolder;
 import android.app.IInstrumentationWatcher;
 import android.app.IUiAutomationConnection;
@@ -114,7 +115,7 @@
     void scheduleCreateBackupAgent(in ApplicationInfo app,
             int backupMode, int userId, int operationType);
     void scheduleDestroyBackupAgent(in ApplicationInfo app, int userId);
-    void scheduleOnNewActivityOptions(IBinder token, in Bundle options);
+    void scheduleOnNewSceneTransitionInfo(IBinder token, in SceneTransitionInfo info);
     void scheduleSuicide();
     void dispatchPackageBroadcast(int cmd, in String[] packages);
     void scheduleCrash(in String msg, int typeId, in Bundle extras);
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 6b5f19a..1b19ecd 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -179,7 +179,7 @@
             }
 
             mActivityThread.handleStartActivity(clientRecord, pendingActions,
-                    null /* activityOptions */);
+                    null /* sceneTransitionInfo */);
             r.curState = STARTED;
             
             if (desiredState == RESUMED) {
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index be420de..62db90f 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -744,20 +744,21 @@
                         params, userId, /* getCropped = */ true);
                 Trace.endSection();
 
-                if (pfd != null) {
-                    try (InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
-                        ImageDecoder.Source src = ImageDecoder.createSource(is.readAllBytes());
-                        return ImageDecoder.decodeBitmap(src, ((decoder, info, source) -> {
-                            // Mutable and hardware config can't be set at the same time.
-                            decoder.setMutableRequired(!hardware);
-                            // Let's do color management
-                            if (cmProxy != null) {
-                                cmProxy.doColorManagement(decoder, info);
-                            }
-                        }));
-                    } catch (OutOfMemoryError | IOException e) {
-                        Log.w(TAG, "Can't decode file", e);
-                    }
+                if (pfd == null) {
+                    return null;
+                }
+                try (InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
+                    ImageDecoder.Source src = ImageDecoder.createSource(context.getResources(), is);
+                    return ImageDecoder.decodeBitmap(src, ((decoder, info, source) -> {
+                        // Mutable and hardware config can't be set at the same time.
+                        decoder.setMutableRequired(!hardware);
+                        // Let's do color management
+                        if (cmProxy != null) {
+                            cmProxy.doColorManagement(decoder, info);
+                        }
+                    }));
+                } catch (OutOfMemoryError | IOException e) {
+                    Log.w(TAG, "Can't decode file", e);
                 }
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 1190bf6..4d53701 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -23,7 +23,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityClient;
-import android.app.ActivityOptions;
+import android.app.ActivityOptions.SceneTransitionInfo;
 import android.app.ActivityThread;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
@@ -73,7 +73,7 @@
     private PersistableBundle mPersistentState;
     private List<ResultInfo> mPendingResults;
     private List<ReferrerIntent> mPendingNewIntents;
-    private ActivityOptions mActivityOptions;
+    private SceneTransitionInfo mSceneTransitionInfo;
     private boolean mIsForward;
     private ProfilerInfo mProfilerInfo;
     private IBinder mAssistToken;
@@ -104,8 +104,8 @@
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
         ActivityClientRecord r = new ActivityClientRecord(mActivityToken, mIntent, mIdent, mInfo,
                 mOverrideConfig, mReferrer, mVoiceInteractor, mState, mPersistentState,
-                mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo,
-                client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
+                mPendingResults, mPendingNewIntents, mSceneTransitionInfo, mIsForward,
+                mProfilerInfo, client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
                 mTaskFragmentToken);
         client.handleLaunchActivity(r, pendingActions, mDeviceId, null /* customIntent */);
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
@@ -136,7 +136,7 @@
             @Nullable IVoiceInteractor voiceInteractor, int procState, @Nullable Bundle state,
             @Nullable PersistableBundle persistentState, @Nullable List<ResultInfo> pendingResults,
             @Nullable List<ReferrerIntent> pendingNewIntents,
-            @Nullable ActivityOptions activityOptions,
+            @Nullable SceneTransitionInfo sceneTransitionInfo,
             boolean isForward, @Nullable ProfilerInfo profilerInfo, @NonNull IBinder assistToken,
             @Nullable IActivityClientController activityClientController,
             @NonNull IBinder shareableActivityToken, boolean launchedFromBubble,
@@ -152,7 +152,7 @@
                 persistentState != null ? new PersistableBundle(persistentState) : null,
                 pendingResults != null ? new ArrayList<>(pendingResults) : null,
                 pendingNewIntents != null ? new ArrayList<>(pendingNewIntents) : null,
-                activityOptions, isForward,
+                sceneTransitionInfo, isForward,
                 profilerInfo != null ? new ProfilerInfo(profilerInfo) : null,
                 assistToken, activityClientController, shareableActivityToken,
                 launchedFromBubble, taskFragmentToken);
@@ -193,7 +193,7 @@
         dest.writePersistableBundle(mPersistentState);
         dest.writeTypedList(mPendingResults, flags);
         dest.writeTypedList(mPendingNewIntents, flags);
-        dest.writeBundle(mActivityOptions != null ? mActivityOptions.toBundle() : null);
+        dest.writeTypedObject(mSceneTransitionInfo, flags);
         dest.writeBoolean(mIsForward);
         dest.writeTypedObject(mProfilerInfo, flags);
         dest.writeStrongBinder(mAssistToken);
@@ -213,7 +213,8 @@
                 in.readPersistableBundle(getClass().getClassLoader()),
                 in.createTypedArrayList(ResultInfo.CREATOR),
                 in.createTypedArrayList(ReferrerIntent.CREATOR),
-                ActivityOptions.fromBundle(in.readBundle()), in.readBoolean(),
+                in.readTypedObject(SceneTransitionInfo.CREATOR),
+                in.readBoolean(),
                 in.readTypedObject(ProfilerInfo.CREATOR),
                 in.readStrongBinder(),
                 IActivityClientController.Stub.asInterface(in.readStrongBinder()),
@@ -253,7 +254,7 @@
                 && areBundlesEqualRoughly(mPersistentState, other.mPersistentState)
                 && Objects.equals(mPendingResults, other.mPendingResults)
                 && Objects.equals(mPendingNewIntents, other.mPendingNewIntents)
-                && (mActivityOptions == null) == (other.mActivityOptions == null)
+                && (mSceneTransitionInfo == null) == (other.mSceneTransitionInfo == null)
                 && mIsForward == other.mIsForward
                 && Objects.equals(mProfilerInfo, other.mProfilerInfo)
                 && Objects.equals(mAssistToken, other.mAssistToken)
@@ -276,7 +277,7 @@
         result = 31 * result + getRoughBundleHashCode(mPersistentState);
         result = 31 * result + Objects.hashCode(mPendingResults);
         result = 31 * result + Objects.hashCode(mPendingNewIntents);
-        result = 31 * result + (mActivityOptions != null ? 1 : 0);
+        result = 31 * result + (mSceneTransitionInfo != null ? 1 : 0);
         result = 31 * result + (mIsForward ? 1 : 0);
         result = 31 * result + Objects.hashCode(mProfilerInfo);
         result = 31 * result + Objects.hashCode(mAssistToken);
@@ -325,7 +326,7 @@
                 + ",persistentState=" + mPersistentState
                 + ",pendingResults=" + mPendingResults
                 + ",pendingNewIntents=" + mPendingNewIntents
-                + ",options=" + mActivityOptions
+                + ",sceneTransitionInfo=" + mSceneTransitionInfo
                 + ",profilerInfo=" + mProfilerInfo
                 + ",assistToken=" + mAssistToken
                 + ",shareableActivityToken=" + mShareableActivityToken + "}";
@@ -340,7 +341,7 @@
             int procState, @Nullable Bundle state, @Nullable PersistableBundle persistentState,
             @Nullable List<ResultInfo> pendingResults,
             @Nullable List<ReferrerIntent> pendingNewIntents,
-            @Nullable ActivityOptions activityOptions, boolean isForward,
+            @Nullable SceneTransitionInfo sceneTransitionInfo, boolean isForward,
             @Nullable ProfilerInfo profilerInfo, @Nullable IBinder assistToken,
             @Nullable IActivityClientController activityClientController,
             @Nullable IBinder shareableActivityToken, boolean launchedFromBubble,
@@ -359,7 +360,7 @@
         instance.mPersistentState = persistentState;
         instance.mPendingResults = pendingResults;
         instance.mPendingNewIntents = pendingNewIntents;
-        instance.mActivityOptions = activityOptions;
+        instance.mSceneTransitionInfo = sceneTransitionInfo;
         instance.mIsForward = isForward;
         instance.mProfilerInfo = profilerInfo;
         instance.mAssistToken = assistToken;
diff --git a/core/java/android/app/servertransaction/StartActivityItem.java b/core/java/android/app/servertransaction/StartActivityItem.java
index 8b98b21..a0f93ce 100644
--- a/core/java/android/app/servertransaction/StartActivityItem.java
+++ b/core/java/android/app/servertransaction/StartActivityItem.java
@@ -20,7 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityOptions;
+import android.app.ActivityOptions.SceneTransitionInfo;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.os.IBinder;
@@ -35,13 +35,13 @@
 
     private static final String TAG = "StartActivityItem";
 
-    private ActivityOptions mActivityOptions;
+    private SceneTransitionInfo mSceneTransitionInfo;
 
     @Override
     public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
             @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "startActivityItem");
-        client.handleStartActivity(r, pendingActions, mActivityOptions);
+        client.handleStartActivity(r, pendingActions, mSceneTransitionInfo);
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -57,13 +57,13 @@
     /** Obtain an instance initialized with provided params. */
     @NonNull
     public static StartActivityItem obtain(@NonNull IBinder activityToken,
-            @Nullable ActivityOptions activityOptions) {
+            @Nullable SceneTransitionInfo sceneTransitionInfo) {
         StartActivityItem instance = ObjectPool.obtain(StartActivityItem.class);
         if (instance == null) {
             instance = new StartActivityItem();
         }
         instance.setActivityToken(activityToken);
-        instance.mActivityOptions = activityOptions;
+        instance.mSceneTransitionInfo = sceneTransitionInfo;
 
         return instance;
     }
@@ -71,7 +71,7 @@
     @Override
     public void recycle() {
         super.recycle();
-        mActivityOptions = null;
+        mSceneTransitionInfo = null;
         ObjectPool.recycle(this);
     }
 
@@ -81,13 +81,13 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         super.writeToParcel(dest, flags);
-        dest.writeBundle(mActivityOptions != null ? mActivityOptions.toBundle() : null);
+        dest.writeTypedObject(mSceneTransitionInfo, flags);
     }
 
     /** Read from Parcel. */
     private StartActivityItem(@NonNull Parcel in) {
         super(in);
-        mActivityOptions = ActivityOptions.fromBundle(in.readBundle());
+        mSceneTransitionInfo = in.readTypedObject(SceneTransitionInfo.CREATOR);
     }
 
     public static final @NonNull Creator<StartActivityItem> CREATOR = new Creator<>() {
@@ -109,21 +109,21 @@
             return false;
         }
         final StartActivityItem other = (StartActivityItem) o;
-        return (mActivityOptions == null) == (other.mActivityOptions == null);
+        return (mSceneTransitionInfo == null) == (other.mSceneTransitionInfo == null);
     }
 
     @Override
     public int hashCode() {
         int result = 17;
         result = 31 * result + super.hashCode();
-        result = 31 * result + (mActivityOptions != null ? 1 : 0);
+        result = 31 * result + (mSceneTransitionInfo != null ? 1 : 0);
         return result;
     }
 
     @Override
     public String toString() {
         return "StartActivityItem{" + super.toString()
-                + ",options=" + mActivityOptions + "}";
+                + ",sceneTransitionInfo=" + mSceneTransitionInfo + "}";
     }
 }
 
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 2e47fee..ba94077 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -324,7 +324,7 @@
                     break;
                 case ON_START:
                     mTransactionHandler.handleStartActivity(r, mPendingActions,
-                            null /* activityOptions */);
+                            null /* sceneTransitionInfo */);
                     break;
                 case ON_RESUME:
                     mTransactionHandler.handleResumeActivity(r, false /* finalStateRequest */,
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 161fa79..cdb92ac 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -458,6 +458,29 @@
             mSystemDataSyncFlags = info.mSystemDataSyncFlags;
         }
 
+        /**
+         * This builder is used specifically to create a new association to be restored to a device
+         * that is potentially using a different user ID from the backed-up device.
+         *
+         * @hide
+         */
+        public Builder(int id, int userId, @NonNull String packageName, AssociationInfo info) {
+            mId = id;
+            mUserId = userId;
+            mPackageName = packageName;
+            mTag = info.mTag;
+            mDeviceMacAddress = info.mDeviceMacAddress;
+            mDisplayName = info.mDisplayName;
+            mDeviceProfile = info.mDeviceProfile;
+            mAssociatedDevice = info.mAssociatedDevice;
+            mSelfManaged = info.mSelfManaged;
+            mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby;
+            mRevoked = info.mRevoked;
+            mTimeApprovedMs = info.mTimeApprovedMs;
+            mLastTimeConnectedMs = info.mLastTimeConnectedMs;
+            mSystemDataSyncFlags = info.mSystemDataSyncFlags;
+        }
+
         /** @hide */
         @FlaggedApi(Flags.FLAG_ASSOCIATION_TAG)
         @TestApi
diff --git a/core/java/android/companion/datatransfer/PermissionSyncRequest.java b/core/java/android/companion/datatransfer/PermissionSyncRequest.java
index 973fca30..34865f0 100644
--- a/core/java/android/companion/datatransfer/PermissionSyncRequest.java
+++ b/core/java/android/companion/datatransfer/PermissionSyncRequest.java
@@ -48,6 +48,15 @@
     }
 
     /** @hide */
+    @Override
+    public PermissionSyncRequest copyWithNewId(int associationId) {
+        PermissionSyncRequest newRequest = new PermissionSyncRequest(associationId);
+        newRequest.mUserId = this.mUserId;
+        newRequest.mUserConsented = this.mUserConsented;
+        return newRequest;
+    }
+
+    /** @hide */
     @NonNull
     public static final Creator<PermissionSyncRequest> CREATOR =
             new Creator<PermissionSyncRequest>() {
diff --git a/core/java/android/companion/datatransfer/SystemDataTransferRequest.java b/core/java/android/companion/datatransfer/SystemDataTransferRequest.java
index 38a553d..c3a2aa4 100644
--- a/core/java/android/companion/datatransfer/SystemDataTransferRequest.java
+++ b/core/java/android/companion/datatransfer/SystemDataTransferRequest.java
@@ -103,4 +103,15 @@
     public int describeContents() {
         return 0;
     }
+
+    /**
+     * Creates a copy of itself with new association ID.
+     *
+     * This method must be implemented to ensure that backup-and-restore can correctly re-map
+     * the restored requests to the restored associations that can potentially have different
+     * IDs than what was originally backed up.
+     *
+     * @hide
+     */
+    public abstract SystemDataTransferRequest copyWithNewId(int associationId);
 }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 7c52238..ee1d117b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4207,7 +4207,9 @@
      * new state of quiet mode. This is only sent to registered receivers, not manifest receivers.
      *
      * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_AVAILABLE} but functions as a
-     * generic broadcast for all profile users.
+     * generic broadcast for all users of type {@link android.os.UserManager#isProfile()}}. In
+     * case of a managed profile, both {@link #ACTION_MANAGED_PROFILE_AVAILABLE} and
+     * {@link #ACTION_PROFILE_AVAILABLE} broadcasts are sent.
      */
     @FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)
     public static final String ACTION_PROFILE_AVAILABLE =
@@ -4221,7 +4223,9 @@
      * new state of quiet mode. This is only sent to registered receivers, not manifest receivers.
      *
      * <p>This broadcast is similar to {@link #ACTION_MANAGED_PROFILE_UNAVAILABLE} but functions as
-     * a generic broadcast for all profile users.
+     * a generic broadcast for all users of type {@link android.os.UserManager#isProfile()}}. In
+     * case of a managed profile, both {@link #ACTION_MANAGED_PROFILE_UNAVAILABLE} and
+     * {@link #ACTION_PROFILE_UNAVAILABLE} broadcasts are sent.
      */
     @FlaggedApi(FLAG_ALLOW_PRIVATE_PROFILE)
     public static final String ACTION_PROFILE_UNAVAILABLE =
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 012b6c4..cdda12e 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -273,9 +273,6 @@
      * to the <code>retailDemo</code> value of
      * {@link android.R.attr#protectionLevel}.
      *
-     * @deprecated This flag has been replaced by the retail demo role and is a no-op since Android
-     *             V.
-     *
      * @hide
      */
     @SystemApi
diff --git a/core/java/android/hardware/face/FaceAuthenticateOptions.java b/core/java/android/hardware/face/FaceAuthenticateOptions.java
index 1c6de04..518f902a 100644
--- a/core/java/android/hardware/face/FaceAuthenticateOptions.java
+++ b/core/java/android/hardware/face/FaceAuthenticateOptions.java
@@ -261,7 +261,7 @@
      * The reason for this operation when requested by the system (sysui),
      * otherwise AUTHENTICATE_REASON_UNKNOWN.
      *
-     * See frameworks/base/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
+     * See packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt
      * for more details about each reason.
      */
     @DataClass.Generated.Member
@@ -524,7 +524,7 @@
          * The reason for this operation when requested by the system (sysui),
          * otherwise AUTHENTICATE_REASON_UNKNOWN.
          *
-         * See frameworks/base/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
+         * See packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt
          * for more details about each reason.
          */
         @DataClass.Generated.Member
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 967a0cc..286cf28 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -95,4 +95,6 @@
     void registerWlcStateListener(in INfcWlcStateListener listener);
     void unregisterWlcStateListener(in INfcWlcStateListener listener);
     WlcLDeviceInfo getWlcLDeviceInfo();
+
+    void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags);
 }
diff --git a/core/java/android/nfc/INfcCardEmulation.aidl b/core/java/android/nfc/INfcCardEmulation.aidl
index 191385a..f4b4604 100644
--- a/core/java/android/nfc/INfcCardEmulation.aidl
+++ b/core/java/android/nfc/INfcCardEmulation.aidl
@@ -43,4 +43,7 @@
     ApduServiceInfo getPreferredPaymentService(int userHandle);
     boolean setServiceEnabledForCategoryOther(int userHandle, in ComponentName app, boolean status);
     boolean isDefaultPaymentRegistered();
+
+    boolean overrideRoutingTable(int userHandle, String protocol, String technology);
+    boolean recoverRoutingTable(int userHandle);
 }
diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java
index 8d75cac..f03fc0a 100644
--- a/core/java/android/nfc/NfcActivityManager.java
+++ b/core/java/android/nfc/NfcActivityManager.java
@@ -112,6 +112,9 @@
         Bundle readerModeExtras = null;
         Binder token;
 
+        int mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
+        int mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
+
         public NfcActivityState(Activity activity) {
             if (activity.isDestroyed()) {
                 throw new IllegalStateException("activity is already destroyed");
@@ -132,6 +135,9 @@
             readerModeFlags = 0;
             readerModeExtras = null;
             token = null;
+
+            mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
+            mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
         }
         @Override
         public String toString() {
@@ -278,6 +284,9 @@
         int readerModeFlags = 0;
         Bundle readerModeExtras = null;
         Binder token;
+        int pollTech;
+        int listenTech;
+
         synchronized (NfcActivityManager.this) {
             NfcActivityState state = findActivityState(activity);
             if (DBG) Log.d(TAG, "onResume() for " + activity + " " + state);
@@ -286,9 +295,15 @@
             token = state.token;
             readerModeFlags = state.readerModeFlags;
             readerModeExtras = state.readerModeExtras;
+
+            pollTech = state.mPollTech;
+            listenTech = state.mListenTech;
         }
         if (readerModeFlags != 0) {
             setReaderMode(token, readerModeFlags, readerModeExtras);
+        } else if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH
+                || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) {
+            changeDiscoveryTech(token, pollTech, listenTech);
         }
         requestNfcServiceCallback();
     }
@@ -298,6 +313,9 @@
     public void onActivityPaused(Activity activity) {
         boolean readerModeFlagsSet;
         Binder token;
+        int pollTech;
+        int listenTech;
+
         synchronized (NfcActivityManager.this) {
             NfcActivityState state = findActivityState(activity);
             if (DBG) Log.d(TAG, "onPause() for " + activity + " " + state);
@@ -305,10 +323,17 @@
             state.resumed = false;
             token = state.token;
             readerModeFlagsSet = state.readerModeFlags != 0;
+
+            pollTech = state.mPollTech;
+            listenTech = state.mListenTech;
         }
         if (readerModeFlagsSet) {
             // Restore default p2p modes
             setReaderMode(token, 0, null);
+        } else if (listenTech != NfcAdapter.FLAG_USE_ALL_TECH
+                || pollTech != NfcAdapter.FLAG_USE_ALL_TECH) {
+            changeDiscoveryTech(token,
+                    NfcAdapter.FLAG_USE_ALL_TECH, NfcAdapter.FLAG_USE_ALL_TECH);
         }
     }
 
@@ -333,4 +358,53 @@
         }
     }
 
+    /** setDiscoveryTechnology() implementation */
+    public void setDiscoveryTech(Activity activity, int pollTech, int listenTech) {
+        boolean isResumed;
+        Binder token;
+        boolean readerModeFlagsSet;
+        synchronized (NfcActivityManager.this) {
+            NfcActivityState state = getActivityState(activity);
+            readerModeFlagsSet = state.readerModeFlags != 0;
+            state.mListenTech = listenTech;
+            state.mPollTech = pollTech;
+            token = state.token;
+            isResumed = state.resumed;
+        }
+        if (!readerModeFlagsSet && isResumed) {
+            changeDiscoveryTech(token, pollTech, listenTech);
+        } else if (readerModeFlagsSet) {
+            throw new IllegalStateException("Cannot be used when the Reader Mode is enabled");
+        }
+    }
+
+    /** resetDiscoveryTechnology() implementation */
+    public void resetDiscoveryTech(Activity activity) {
+        boolean isResumed;
+        Binder token;
+        boolean readerModeFlagsSet;
+        synchronized (NfcActivityManager.this) {
+            NfcActivityState state = getActivityState(activity);
+            readerModeFlagsSet = state.readerModeFlags != 0;
+            state.mListenTech = NfcAdapter.FLAG_USE_ALL_TECH;
+            state.mPollTech = NfcAdapter.FLAG_USE_ALL_TECH;
+            token = state.token;
+            isResumed = state.resumed;
+        }
+        if (readerModeFlagsSet) {
+            disableReaderMode(activity);
+        } else if (isResumed) {
+            changeDiscoveryTech(token, NfcAdapter.FLAG_USE_ALL_TECH, NfcAdapter.FLAG_USE_ALL_TECH);
+        }
+
+    }
+
+    private void changeDiscoveryTech(Binder token, int pollTech, int listenTech) {
+        try {
+            NfcAdapter.sService.updateDiscoveryTechnology(token, pollTech, listenTech);
+        } catch (RemoteException e) {
+            mAdapter.attemptDeadServiceRecovery(e);
+        }
+    }
+
 }
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 21e23ae..5a40e42 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -333,6 +333,19 @@
      */
     public static final int FLAG_READER_NFC_BARCODE = 0x10;
 
+    /** @hide */
+    @IntDef(flag = true, prefix = {"FLAG_READER_"}, value = {
+        FLAG_READER_KEEP,
+        FLAG_READER_DISABLE,
+        FLAG_READER_NFC_A,
+        FLAG_READER_NFC_B,
+        FLAG_READER_NFC_F,
+        FLAG_READER_NFC_V,
+        FLAG_READER_NFC_BARCODE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PollTechnology {}
+
     /**
      * Flag for use with {@link #enableReaderMode(Activity, ReaderCallback, int, Bundle)}.
      * <p>
@@ -360,6 +373,76 @@
     public static final String EXTRA_READER_PRESENCE_CHECK_DELAY = "presence";
 
     /**
+     * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag enables listening for Nfc-A technology.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_LISTEN_NFC_PASSIVE_A = 0x1;
+
+    /**
+     * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag enables listening for Nfc-B technology.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_LISTEN_NFC_PASSIVE_B = 1 << 1;
+
+    /**
+     * Flag for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag enables listening for Nfc-F technology.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_LISTEN_NFC_PASSIVE_F = 1 << 2;
+
+    /**
+     * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag disables listening.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_LISTEN_DISABLE = 0x0;
+
+    /**
+     * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag disables polling.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_READER_DISABLE = 0x0;
+
+    /**
+     * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag makes listening to use current flags.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_LISTEN_KEEP = -1;
+
+    /**
+     * Flags for use with {@link #setDiscoveryTechnology(Activity, int, int)}.
+     * <p>
+     * Setting this flag makes polling to use current flags.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public static final int FLAG_READER_KEEP = -1;
+
+    /** @hide */
+    public static final int FLAG_USE_ALL_TECH = 0xff;
+
+    /** @hide */
+    @IntDef(flag = true, prefix = {"FLAG_LISTEN_"}, value = {
+        FLAG_LISTEN_KEEP,
+        FLAG_LISTEN_DISABLE,
+        FLAG_LISTEN_NFC_PASSIVE_A,
+        FLAG_LISTEN_NFC_PASSIVE_B,
+        FLAG_LISTEN_NFC_PASSIVE_F
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ListenTechnology {}
+
+    /**
      * @hide
      * @removed
      */
@@ -437,12 +520,14 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface TagIntentAppPreferenceResult {}
 
-    // Guarded by NfcAdapter.class
+    // Guarded by sLock
     static boolean sIsInitialized = false;
     static boolean sHasNfcFeature;
     static boolean sHasCeFeature;
     static boolean sHasNfcWlcFeature;
 
+    static Object sLock = new Object();
+
     // Final after first constructor, except for
     // attemptDeadServiceRecovery() when NFC crashes - we accept a best effort
     // recovery
@@ -1235,7 +1320,7 @@
     @java.lang.Deprecated
     @UnsupportedAppUsage
     public void setBeamPushUris(Uri[] uris, Activity activity) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1305,7 +1390,7 @@
     @java.lang.Deprecated
     @UnsupportedAppUsage
     public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1390,7 +1475,7 @@
     @UnsupportedAppUsage
     public void setNdefPushMessage(NdefMessage message, Activity activity,
             Activity ... activities) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1404,7 +1489,7 @@
     @SystemApi
     @UnsupportedAppUsage
     public void setNdefPushMessage(NdefMessage message, Activity activity, int flags) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1483,7 +1568,7 @@
     @UnsupportedAppUsage
     public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
             Activity ... activities) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1534,7 +1619,7 @@
     @UnsupportedAppUsage
     public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback,
             Activity activity, Activity ... activities) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1577,7 +1662,7 @@
      */
     public void enableForegroundDispatch(Activity activity, PendingIntent intent,
             IntentFilter[] filters, String[][] techLists) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1612,7 +1697,7 @@
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      */
     public void disableForegroundDispatch(Activity activity) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1648,7 +1733,7 @@
      */
     public void enableReaderMode(Activity activity, ReaderCallback callback, int flags,
             Bundle extras) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1665,7 +1750,7 @@
      * @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
      */
     public void disableReaderMode(Activity activity) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1693,7 +1778,7 @@
     @FlaggedApi(Flags.FLAG_ENABLE_NFC_MAINLINE)
     @SuppressLint("VisiblySynchronized")
     public void setReaderMode(boolean enablePolling) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1708,6 +1793,80 @@
     }
 
     /**
+     * Set the NFC controller to enable specific poll/listen technologies,
+     * as specified in parameters, while this Activity is in the foreground.
+     *
+     * Use {@link #FLAG_READER_KEEP} to keep current polling technology.
+     * Use {@link #FLAG_LISTEN_KEEP} to keep current listenig technology.
+     * Use {@link #FLAG_READER_DISABLE} to disable polling.
+     * Use {@link #FLAG_LISTEN_DISABLE} to disable listening.
+     * Also refer to {@link #resetDiscoveryTechnology(Activity)} to restore these changes.
+     * </p>
+     * The pollTech, listenTech parameters can be one or several of below list.
+     * <pre>
+     *                    Poll                    Listen
+     *  Passive A         0x01   (NFC_A)           0x01  (NFC_PASSIVE_A)
+     *  Passive B         0x02   (NFC_B)           0x02  (NFC_PASSIVE_B)
+     *  Passive F         0x04   (NFC_F)           0x04  (NFC_PASSIVE_F)
+     *  ISO 15693         0x08   (NFC_V)             -
+     *  Kovio             0x10   (NFC_BARCODE)       -
+     * </pre>
+     * <p>Example usage in an Activity that requires to disable poll,
+     * keep current listen technologies:
+     * <pre>
+     * protected void onResume() {
+     *     mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
+     *     mNfcAdapter.setDiscoveryTechnology(this,
+     *         NfcAdapter.FLAG_READER_DISABLE, NfcAdapter.FLAG_LISTEN_KEEP);
+     * }</pre></p>
+     * @param activity The Activity that requests NFC controller to enable specific technologies.
+     * @param pollTech Flags indicating poll technologies.
+     * @param listenTech Flags indicating listen technologies.
+     * @throws UnsupportedOperationException if FEATURE_NFC,
+     * FEATURE_NFC_HOST_CARD_EMULATION, FEATURE_NFC_HOST_CARD_EMULATION_NFCF are unavailable.
+     */
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public void setDiscoveryTechnology(@NonNull Activity activity,
+            @PollTechnology int pollTech, @ListenTechnology int listenTech) {
+        if (listenTech == FLAG_LISTEN_DISABLE) {
+            synchronized (sLock) {
+                if (!sHasNfcFeature) {
+                    throw new UnsupportedOperationException();
+                }
+            }
+            mNfcActivityManager.enableReaderMode(activity, null, pollTech, null);
+            return;
+        }
+        if (pollTech == FLAG_READER_DISABLE) {
+            synchronized (sLock) {
+                if (!sHasCeFeature) {
+                    throw new UnsupportedOperationException();
+                }
+            }
+        } else {
+            synchronized (sLock) {
+                if (!sHasNfcFeature || !sHasCeFeature) {
+                    throw new UnsupportedOperationException();
+                }
+            }
+        }
+        mNfcActivityManager.setDiscoveryTech(activity, pollTech, listenTech);
+    }
+
+    /**
+     * Restore the poll/listen technologies of NFC controller,
+     * which were changed by {@link #setDiscoveryTechnology(Activity , int , int)}
+     *
+     * @param activity The Activity that requests to changed technologies.
+     */
+
+    @FlaggedApi(Flags.FLAG_ENABLE_NFC_SET_DISCOVERY_TECH)
+    public void resetDiscoveryTechnology(@NonNull Activity activity) {
+        mNfcActivityManager.resetDiscoveryTech(activity);
+    }
+
+    /**
      * Manually invoke Android Beam to share data.
      *
      * <p>The Android Beam animation is normally only shown when two NFC-capable
@@ -1737,7 +1896,7 @@
     @java.lang.Deprecated
     @UnsupportedAppUsage
     public boolean invokeBeam(Activity activity) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1775,7 +1934,7 @@
     @Deprecated
     @UnsupportedAppUsage
     public void enableForegroundNdefPush(Activity activity, NdefMessage message) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -1805,7 +1964,7 @@
     @Deprecated
     @UnsupportedAppUsage
     public void disableForegroundNdefPush(Activity activity) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -2085,7 +2244,7 @@
     @java.lang.Deprecated
     @UnsupportedAppUsage
     public boolean isNdefPushEnabled() {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -2199,7 +2358,7 @@
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public boolean addNfcUnlockHandler(final NfcUnlockHandler unlockHandler,
                                        String[] tagTechnologies) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
@@ -2248,7 +2407,7 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
     public boolean removeNfcUnlockHandler(NfcUnlockHandler unlockHandler) {
-        synchronized (NfcAdapter.class) {
+        synchronized (sLock) {
             if (!sHasNfcFeature) {
                 throw new UnsupportedOperationException();
             }
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index 58b6179..ad86d70 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -998,6 +998,87 @@
         }
     }
 
+     /**
+      * Setting NFC controller routing table, which includes Protocol Route and Technology Route,
+      * while this Activity is in the foreground.
+      *
+      * The parameter set to null can be used to keep current values for that entry.
+      * <p>
+      * Example usage in an Activity that requires to set proto route to "ESE" and keep tech route:
+      * <pre>
+      * protected void onResume() {
+      *     mNfcAdapter.overrideRoutingTable(this , "ESE" , null);
+      * }</pre>
+      * </p>
+      * Also activities must call this method when it goes to the background,
+      * with all parameters set to null.
+      * @param activity The Activity that requests NFC controller routing table to be changed.
+      * @param protocol ISO-DEP route destination, which can be "DH" or "UICC" or "ESE".
+      * @param technology Tech-A, Tech-B route destination, which can be "DH" or "UICC" or "ESE".
+      * @return true if operation is successful and false otherwise
+      *
+      * This is a high risk API and only included to support mainline effort
+      * @hide
+      */
+    public boolean overrideRoutingTable(Activity activity, String protocol, String technology) {
+        if (activity == null) {
+            throw new NullPointerException("activity or service or category is null");
+        }
+        if (!activity.isResumed()) {
+            throw new IllegalArgumentException("Activity must be resumed.");
+        }
+        try {
+            return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology);
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.overrideRoutingTable(UserHandle.myUserId(), protocol, technology);
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Restore the NFC controller routing table,
+     * which was changed by {@link #overrideRoutingTable(Activity, String, String)}
+     *
+     * @param activity The Activity that requested NFC controller routing table to be changed.
+     * @return true if operation is successful and false otherwise
+     *
+     * @hide
+     */
+    public boolean recoverRoutingTable(Activity activity) {
+        if (activity == null) {
+            throw new NullPointerException("activity is null");
+        }
+        if (!activity.isResumed()) {
+            throw new IllegalArgumentException("Activity must be resumed.");
+        }
+        try {
+            return sService.recoverRoutingTable(UserHandle.myUserId());
+        } catch (RemoteException e) {
+            // Try one more time
+            recoverService();
+            if (sService == null) {
+                Log.e(TAG, "Failed to recover CardEmulationService.");
+                return false;
+            }
+            try {
+                return sService.recoverRoutingTable(UserHandle.myUserId());
+            } catch (RemoteException ee) {
+                Log.e(TAG, "Failed to reach CardEmulationService.");
+                return false;
+            }
+        }
+    }
+
     void recoverService() {
         NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
         sService = adapter.getCardEmulationService();
diff --git a/core/java/android/nfc/flags.aconfig b/core/java/android/nfc/flags.aconfig
index ce4f777..01a4570 100644
--- a/core/java/android/nfc/flags.aconfig
+++ b/core/java/android/nfc/flags.aconfig
@@ -62,3 +62,10 @@
     description: "Flag for NFC charging changes"
     bug: "292143899"
 }
+
+flag {
+    name: "enable_nfc_set_discovery_tech"
+    namespace: "nfc"
+    description: "Flag for NFC set discovery tech API"
+    bug: "300351519"
+}
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index f71c269..07f7690 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -18,8 +18,6 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
-
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -47,11 +45,8 @@
 import android.util.Log;
 import android.view.Display;
 
-import libcore.io.Streams;
-
 import java.io.ByteArrayInputStream;
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileWriter;
 import java.io.IOException;
@@ -75,7 +70,6 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
-import java.util.zip.ZipInputStream;
 
 import sun.security.pkcs.PKCS7;
 import sun.security.pkcs.SignerInfo;
@@ -426,72 +420,43 @@
         } finally {
             raf.close();
         }
-
-        // Additionally verify the package compatibility.
-        if (!readAndVerifyPackageCompatibilityEntry(packageFile)) {
-            throw new SignatureException("package compatibility verification failed");
-        }
     }
 
     /**
      * Verifies the compatibility entry from an {@link InputStream}.
      *
-     * @return the verification result.
+     * @param inputStream The stream that contains the package compatibility info.
+     * @throws IOException Never.
+     * @return {@code true}.
+     * @deprecated This function no longer checks {@code inputStream} and
+     *   unconditionally returns true. Instead, check compatibility when the
+     *   OTA package is generated.
      */
-    @UnsupportedAppUsage
+    @Deprecated
+    @UnsupportedAppUsage(
+            publicAlternatives = "Use {@code true} directly",
+            maxTargetSdk = Build.VERSION_CODES.VANILLA_ICE_CREAM)
     private static boolean verifyPackageCompatibility(InputStream inputStream) throws IOException {
-        ArrayList<String> list = new ArrayList<>();
-        ZipInputStream zis = new ZipInputStream(inputStream);
-        ZipEntry entry;
-        while ((entry = zis.getNextEntry()) != null) {
-            long entrySize = entry.getSize();
-            if (entrySize > Integer.MAX_VALUE || entrySize < 0) {
-                throw new IOException(
-                        "invalid entry size (" + entrySize + ") in the compatibility file");
-            }
-            byte[] bytes = new byte[(int) entrySize];
-            Streams.readFully(zis, bytes);
-            list.add(new String(bytes, UTF_8));
-        }
-        if (list.isEmpty()) {
-            throw new IOException("no entries found in the compatibility file");
-        }
-        return (VintfObject.verify(list.toArray(new String[list.size()])) == 0);
-    }
-
-    /**
-     * Reads and verifies the compatibility entry in an OTA zip package. The compatibility entry is
-     * a zip file (inside the OTA package zip).
-     *
-     * @return {@code true} if the entry doesn't exist or verification passes.
-     */
-    private static boolean readAndVerifyPackageCompatibilityEntry(File packageFile)
-            throws IOException {
-        try (ZipFile zip = new ZipFile(packageFile)) {
-            ZipEntry entry = zip.getEntry("compatibility.zip");
-            if (entry == null) {
-                return true;
-            }
-            InputStream inputStream = zip.getInputStream(entry);
-            return verifyPackageCompatibility(inputStream);
-        }
+        return true;
     }
 
     /**
      * Verifies the package compatibility info against the current system.
      *
      * @param compatibilityFile the {@link File} that contains the package compatibility info.
-     * @throws IOException if there were any errors reading the compatibility file.
-     * @return the compatibility verification result.
+     * @throws IOException Never.
+     * @return {@code true}
+     * @deprecated This function no longer checks {@code compatibilityFile} and
+     *   unconditionally returns true. Instead, check compatibility when the
+     *   OTA package is generated.
      *
      * {@hide}
      */
+    @Deprecated
     @SystemApi
     @SuppressLint("RequiresPermission")
     public static boolean verifyPackageCompatibility(File compatibilityFile) throws IOException {
-        try (InputStream inputStream = new FileInputStream(compatibilityFile)) {
-            return verifyPackageCompatibility(inputStream);
-        }
+        return true;
     }
 
     /**
diff --git a/core/java/android/os/UpdateEngineStable.java b/core/java/android/os/UpdateEngineStable.java
new file mode 100644
index 0000000..9e2593e
--- /dev/null
+++ b/core/java/android/os/UpdateEngineStable.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.annotation.IntDef;
+
+/**
+ * UpdateEngineStable handles calls to the update engine stalbe which takes care of A/B OTA updates.
+ * This interface has lesser functionalities than UpdateEngine and doesn't allow cancel.
+ *
+ * <p>The minimal flow is:
+ *
+ * <ol>
+ *   <li>Create a new UpdateEngineStable instance.
+ *   <li>Call {@link #bind}, provide callback function.
+ *   <li>Call {@link #applyPayloadFd}.
+ * </ol>
+ *
+ * The APIs defined in this class and UpdateEngineStableCallback class must be in sync with the ones
+ * in {@code system/update_engine/stable/android/os/IUpdateEngineStable.aidl} and {@code
+ * ssystem/update_engine/stable/android/os/IUpdateEngineStableCallback.aidl}.
+ *
+ * @hide
+ */
+public class UpdateEngineStable {
+    private static final String TAG = "UpdateEngineStable";
+
+    private static final String UPDATE_ENGINE_STABLE_SERVICE =
+            "android.os.UpdateEngineStableService";
+
+    /**
+     * Error codes from update engine upon finishing a call to {@link applyPayloadFd}. Values will
+     * be passed via the callback function {@link
+     * UpdateEngineStableCallback#onPayloadApplicationComplete}. Values must agree with the ones in
+     * {@code system/update_engine/common/error_code.h}.
+     */
+    /** @hide */
+    @IntDef(
+            value = {
+                UpdateEngine.ErrorCodeConstants.SUCCESS,
+                UpdateEngine.ErrorCodeConstants.ERROR,
+                UpdateEngine.ErrorCodeConstants.FILESYSTEM_COPIER_ERROR,
+                UpdateEngine.ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR,
+                UpdateEngine.ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR,
+                UpdateEngine.ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR,
+                UpdateEngine.ErrorCodeConstants.KERNEL_DEVICE_OPEN_ERROR,
+                UpdateEngine.ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR,
+                UpdateEngine.ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR,
+                UpdateEngine.ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR,
+                UpdateEngine.ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR,
+                UpdateEngine.ErrorCodeConstants.PAYLOAD_TIMESTAMP_ERROR,
+                UpdateEngine.ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE,
+                UpdateEngine.ErrorCodeConstants.NOT_ENOUGH_SPACE,
+                UpdateEngine.ErrorCodeConstants.DEVICE_CORRUPTED,
+            })
+    public @interface ErrorCode {}
+
+    private final IUpdateEngineStable mUpdateEngineStable;
+    private IUpdateEngineStableCallback mUpdateEngineStableCallback = null;
+    private final Object mUpdateEngineStableCallbackLock = new Object();
+
+    /**
+     * Creates a new instance.
+     *
+     * @hide
+     */
+    public UpdateEngineStable() {
+        mUpdateEngineStable =
+                IUpdateEngineStable.Stub.asInterface(
+                        ServiceManager.getService(UPDATE_ENGINE_STABLE_SERVICE));
+        if (mUpdateEngineStable == null) {
+            throw new IllegalStateException("Failed to find " + UPDATE_ENGINE_STABLE_SERVICE);
+        }
+    }
+
+    /**
+     * Prepares this instance for use. The callback will be notified on any status change, and when
+     * the update completes. A handler can be supplied to control which thread runs the callback, or
+     * null.
+     *
+     * @hide
+     */
+    public boolean bind(final UpdateEngineStableCallback callback, final Handler handler) {
+        synchronized (mUpdateEngineStableCallbackLock) {
+            mUpdateEngineStableCallback =
+                    new IUpdateEngineStableCallback.Stub() {
+                        @Override
+                        public void onStatusUpdate(final int status, final float percent) {
+                            if (handler != null) {
+                                handler.post(
+                                        new Runnable() {
+                                            @Override
+                                            public void run() {
+                                                callback.onStatusUpdate(status, percent);
+                                            }
+                                        });
+                            } else {
+                                callback.onStatusUpdate(status, percent);
+                            }
+                        }
+
+                        @Override
+                        public void onPayloadApplicationComplete(final int errorCode) {
+                            if (handler != null) {
+                                handler.post(
+                                        new Runnable() {
+                                            @Override
+                                            public void run() {
+                                                callback.onPayloadApplicationComplete(errorCode);
+                                            }
+                                        });
+                            } else {
+                                callback.onPayloadApplicationComplete(errorCode);
+                            }
+                        }
+
+                        @Override
+                        public int getInterfaceVersion() {
+                            return super.VERSION;
+                        }
+
+                        @Override
+                        public String getInterfaceHash() {
+                            return super.HASH;
+                        }
+                    };
+
+            try {
+                return mUpdateEngineStable.bind(mUpdateEngineStableCallback);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Equivalent to {@code bind(callback, null)}.
+     *
+     * @hide
+     */
+    public boolean bind(final UpdateEngineStableCallback callback) {
+        return bind(callback, null);
+    }
+
+    /**
+     * Applies payload from given ParcelFileDescriptor. Usage is same as UpdateEngine#applyPayload
+     *
+     * @hide
+     */
+    public void applyPayloadFd(
+            ParcelFileDescriptor fd, long offset, long size, String[] headerKeyValuePairs) {
+        try {
+            mUpdateEngineStable.applyPayloadFd(fd, offset, size, headerKeyValuePairs);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unbinds the last bound callback function.
+     *
+     * @hide
+     */
+    public boolean unbind() {
+        synchronized (mUpdateEngineStableCallbackLock) {
+            if (mUpdateEngineStableCallback == null) {
+                return true;
+            }
+            try {
+                boolean result = mUpdateEngineStable.unbind(mUpdateEngineStableCallback);
+                mUpdateEngineStableCallback = null;
+                return result;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+}
diff --git a/core/java/android/os/UpdateEngineStableCallback.java b/core/java/android/os/UpdateEngineStableCallback.java
new file mode 100644
index 0000000..4bcfb4b
--- /dev/null
+++ b/core/java/android/os/UpdateEngineStableCallback.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Callback function for UpdateEngineStable. Used to keep the caller up to date with progress, so
+ * the UI (if any) can be updated.
+ *
+ * <p>The APIs defined in this class and UpdateEngineStable class must be in sync with the ones in
+ * system/update_engine/stable/android/os/IUpdateEngineStable.aidl and
+ * system/update_engine/stable/android/os/IUpdateEngineStableCallback.aidl.
+ *
+ * <p>{@hide}
+ */
+public abstract class UpdateEngineStableCallback {
+
+    /**
+     * Invoked when anything changes. The value of {@code status} will be one of the values from
+     * {@link UpdateEngine.UpdateStatusConstants}, and {@code percent} will be valid
+     *
+     * @hide
+     */
+    public abstract void onStatusUpdate(int status, float percent);
+
+    /**
+     * Invoked when the payload has been applied, whether successfully or unsuccessfully. The value
+     * of {@code errorCode} will be one of the values from {@link UpdateEngine.ErrorCodeConstants}.
+     *
+     * @hide
+     */
+    public abstract void onPayloadApplicationComplete(@UpdateEngineStable.ErrorCode int errorCode);
+}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index d09c229..fae7cb3 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -64,3 +64,10 @@
   description: "enable Permission PREPARE_FACTORY_RESET."
   bug: "302016478"
 }
+
+flag {
+    name: "retail_demo_role_enabled"
+    namespace: "permissions"
+    description: "default retail demo role holder"
+    bug: "274132354"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 54cc5f4..84fddcb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7428,6 +7428,20 @@
         public static final String DEFAULT_INPUT_METHOD = "default_input_method";
 
         /**
+         * Used only by {@link com.android.server.inputmethod.InputMethodManagerService} as a
+         * temporary data store of {@link #DEFAULT_INPUT_METHOD} while a virtual-device-specific
+         * input method is set as default.</p>
+         *
+         * <p>This should be considered to be an implementation detail of
+         * {@link com.android.server.inputmethod.InputMethodManagerService}.  Other system
+         * components should never rely on this value.</p>
+         *
+         * @see #DEFAULT_INPUT_METHOD
+         * @hide
+         */
+        public static final String DEFAULT_DEVICE_INPUT_METHOD = "default_device_input_method";
+
+        /**
          * Setting to record the input method subtype used by default, holding the ID
          * of the desired method.
          */
@@ -14347,6 +14361,19 @@
                 "mute_alarm_stream_with_ringer_mode";
 
         /**
+         * The user's choice for whether or not Alarm stream should always be muted with Ringer.
+         *
+         * <p>Note that this is different from {@link #MUTE_ALARM_STREAM_WITH_RINGER_MODE}, which
+         * controls the real state of whether or not the Alarm stream and Ringer association occurs.
+         * The two Settings are not necessarily equal, if the final decision for the association
+         * depends on factors beyond the user's preference.
+         *
+         * @hide
+         */
+        public static final String MUTE_ALARM_STREAM_WITH_RINGER_MODE_USER_PREFERENCE =
+                "mute_alarm_stream_with_ringer_mode_user_preference";
+
+        /**
          * Overlay display devices setting.
          * The associated value is a specially formatted string that describes the
          * size and density of simulated secondary display devices.
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index e4af2da..d47ff2e 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -368,11 +368,13 @@
          * <p>
          * As of Android 11 apps will need specific permission to query other packages. To use
          * this method an app must include in their AndroidManifest:
+         * <pre>{@code
          * <queries>
          *   <intent>
          *     <action android:name="android.provider.Telephony.SMS_DELIVER"/>
          *   </intent>
          * </queries>
+         * }</pre>
          * Which will allow them to query packages which declare intent filters that include
          * the {@link android.provider.Telephony.Sms.Intents#SMS_DELIVER_ACTION} intent.
          * </p>
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 692dad4..1a2be15 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1439,12 +1439,12 @@
                                 // to make sure the wallpaper is stopped even after the events
                                 // onSurfaceCreated() and onSurfaceChanged().
                                 if (noConsecutiveVisibilityEvents()) {
-                                    if (DEBUG) Log.v(TAG, "toggling doVisibilityChanged");
-                                    Trace.beginSection("WPMS.Engine.doVisibilityChanged-true");
-                                    doVisibilityChanged(true);
+                                    if (DEBUG) Log.v(TAG, "toggling onVisibilityChanged");
+                                    Trace.beginSection("WPMS.Engine.onVisibilityChanged-true");
+                                    onVisibilityChanged(true);
                                     Trace.endSection();
-                                    Trace.beginSection("WPMS.Engine.doVisibilityChanged-false");
-                                    doVisibilityChanged(false);
+                                    Trace.beginSection("WPMS.Engine.onVisibilityChanged-false");
+                                    onVisibilityChanged(false);
                                     Trace.endSection();
                                 } else {
                                     if (DEBUG) {
diff --git a/core/java/android/view/DisplayAddress.java b/core/java/android/view/DisplayAddress.java
index 99e811a..c3c4ab5 100644
--- a/core/java/android/view/DisplayAddress.java
+++ b/core/java/android/view/DisplayAddress.java
@@ -138,6 +138,30 @@
             out.writeLong(mPhysicalDisplayId);
         }
 
+        /**
+         * This method is meant to check to see if the ports match
+         * @param a1 Address to compare
+         * @param a2 Address to compare
+         *
+         * @return true if the arguments have the same port, and at least one does not specify
+         *         a model.
+         */
+        public static boolean isPortMatch(DisplayAddress a1, DisplayAddress a2) {
+            // Both displays must be of type Physical
+            if (!(a1 instanceof Physical && a2 instanceof Physical)) {
+                return false;
+            }
+            Physical p1 = (Physical) a1;
+            Physical p2 = (Physical) a2;
+
+            // If both addresses specify a model, fallback to a basic match check (which
+            // also checks the port).
+            if (p1.getModel() != null && p2.getModel() != null) {
+                return p1.equals(p2);
+            }
+            return p1.getPort() == p2.getPort();
+        }
+
         private Physical(long physicalDisplayId) {
             mPhysicalDisplayId = physicalDisplayId;
         }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 61cf126..f61ed51 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3163,6 +3163,12 @@
         public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 1 << 10;
 
         /**
+         * Flag to indicate that the window is forcibly to go edge-to-edge.
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED = 1 << 11;
+
+        /**
          * Flag to indicate that the window frame should be the requested frame adding the display
          * cutout frame. This will only be applied if a specific size smaller than the parent frame
          * is given, and the window is covering the display cutout. The extended frame will not be
@@ -3338,6 +3344,7 @@
                 PRIVATE_FLAG_SYSTEM_ERROR,
                 PRIVATE_FLAG_OPTIMIZE_MEASURE,
                 PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
+                PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED,
                 PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
                 PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
                 PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
@@ -3400,6 +3407,10 @@
                         equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
                         name = "DISABLE_WALLPAPER_TOUCH_EVENTS"),
                 @ViewDebug.FlagToString(
+                        mask = PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED,
+                        equals = PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED,
+                        name = "EDGE_TO_EDGE_ENFORCED"),
+                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
                         equals = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
                         name = "LAYOUT_SIZE_EXTENDED_BY_CUTOUT"),
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 0cc19fb..48f8f1b 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -24,6 +24,13 @@
 }
 
 flag {
+    namespace: "accessibility"
+    name: "braille_display_hid"
+    description: "Enables new APIs for an AccessibilityService to communicate with a HID Braille display"
+    bug: "303522222"
+}
+
+flag {
     name: "cleanup_accessibility_warning_dialog"
     namespace: "accessibility"
     description: "Cleans up duplicated or broken logic surrounding the accessibility warning dialog."
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 216acdc..3366a7e 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -38,6 +38,14 @@
 }
 
 flag {
+  name: "draw_magnifier_border_outside_wmlock"
+  namespace: "windowing_frontend"
+  description: "Avoid holding WM locks for a long time when executing lockCanvas"
+  bug: "316075123"
+  is_fixed_read_only: true
+}
+
+flag {
   name: "introduce_smoother_dimmer"
   namespace: "windowing_frontend"
   description: "Refactor dim to fix flickers"
diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl
index 36b7ee5..d62c8f3 100644
--- a/core/java/com/android/internal/policy/IKeyguardService.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardService.aidl
@@ -130,4 +130,10 @@
      * Note that it's called only if the device is interactive.
      */
     void onSystemKeyPressed(int keycode);
+
+    /**
+     * Requests to show the keyguard immediately without locking the device. Keyguard will show
+     * whether a screen lock was configured or not (including if screen lock is SWIPE or NONE).
+     */
+    void showDismissibleKeyguard();
 }
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 31910ac..54fdcc6 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -31,6 +31,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 
@@ -294,9 +295,9 @@
     private int mFrameResource = 0;
 
     private int mTextColor = 0;
-    int mStatusBarColor = 0;
-    int mNavigationBarColor = 0;
-    int mNavigationBarDividerColor = 0;
+    int mStatusBarColor = Color.TRANSPARENT;
+    int mNavigationBarColor = Color.TRANSPARENT;
+    int mNavigationBarDividerColor = Color.TRANSPARENT;
     private boolean mForcedStatusBarColor = false;
     private boolean mForcedNavigationBarColor = false;
 
@@ -393,6 +394,7 @@
                         || (CompatChanges.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)
                                 && Flags.enforceEdgeToEdge());
         if (mEdgeToEdgeEnforced) {
+            getAttributes().privateFlags |= PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
             mDecorFitsSystemWindows = false;
         }
     }
@@ -2548,17 +2550,10 @@
         final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
         final boolean targetPreQ = targetSdk < Build.VERSION_CODES.Q;
 
-        if (!mForcedStatusBarColor) {
-            final int statusBarCompatibleColor = context.getColor(R.color.status_bar_compatible);
-            final int statusBarDefaultColor = context.getColor(R.color.status_bar_default);
-            final int statusBarColor = a.getColor(R.styleable.Window_statusBarColor,
-                    statusBarDefaultColor);
-
-            mStatusBarColor = statusBarColor == statusBarDefaultColor && !mEdgeToEdgeEnforced
-                    ? statusBarCompatibleColor
-                    : statusBarColor;
+        if (!mForcedStatusBarColor && !mEdgeToEdgeEnforced) {
+            mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, Color.BLACK);
         }
-        if (!mForcedNavigationBarColor) {
+        if (!mForcedNavigationBarColor && !mEdgeToEdgeEnforced) {
             final int navBarCompatibleColor = context.getColor(R.color.navigation_bar_compatible);
             final int navBarDefaultColor = context.getColor(R.color.navigation_bar_default);
             final int navBarColor = a.getColor(R.styleable.Window_navigationBarColor,
@@ -2566,7 +2561,6 @@
 
             mNavigationBarColor =
                     navBarColor == navBarDefaultColor
-                            && !mEdgeToEdgeEnforced
                             && !context.getResources().getBoolean(
                                     R.bool.config_navBarDefaultTransparent)
                     ? navBarCompatibleColor
@@ -2575,7 +2569,7 @@
             mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
                     Color.TRANSPARENT);
         }
-        if (!targetPreQ) {
+        if (!targetPreQ && !mEdgeToEdgeEnforced) {
             mEnsureStatusBarContrastWhenTransparent = a.getBoolean(
                     R.styleable.Window_enforceStatusBarContrast, false);
             mEnsureNavigationBarContrastWhenTransparent = a.getBoolean(
@@ -3899,6 +3893,9 @@
 
     @Override
     public void setStatusBarColor(int color) {
+        if (mEdgeToEdgeEnforced) {
+            return;
+        }
         if (mStatusBarColor == color && mForcedStatusBarColor) {
             return;
         }
@@ -3920,6 +3917,9 @@
 
     @Override
     public void setNavigationBarColor(int color) {
+        if (mEdgeToEdgeEnforced) {
+            return;
+        }
         if (mNavigationBarColor == color && mForcedNavigationBarColor) {
             return;
         }
@@ -3936,6 +3936,9 @@
 
     @Override
     public void setNavigationBarDividerColor(int navigationBarDividerColor) {
+        if (mEdgeToEdgeEnforced) {
+            return;
+        }
         mNavigationBarDividerColor = navigationBarDividerColor;
         if (mDecor != null) {
             mDecor.updateColorViews(null, false /* animate */);
@@ -3949,6 +3952,9 @@
 
     @Override
     public void setStatusBarContrastEnforced(boolean ensureContrast) {
+        if (mEdgeToEdgeEnforced) {
+            return;
+        }
         mEnsureStatusBarContrastWhenTransparent = ensureContrast;
         if (mDecor != null) {
             mDecor.updateColorViews(null, false /* animate */);
@@ -3962,6 +3968,9 @@
 
     @Override
     public void setNavigationBarContrastEnforced(boolean enforceContrast) {
+        if (mEdgeToEdgeEnforced) {
+            return;
+        }
         mEnsureNavigationBarContrastWhenTransparent = enforceContrast;
         if (mDecor != null) {
             mDecor.updateColorViews(null, false /* animate */);
@@ -4031,6 +4040,9 @@
 
     @Override
     public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) {
+        if (mEdgeToEdgeEnforced) {
+            return;
+        }
         mDecorFitsSystemWindows = decorFitsSystemWindows;
         applyDecorFitsSystemWindows();
     }
diff --git a/core/jni/hwbinder/EphemeralStorage.cpp b/core/jni/hwbinder/EphemeralStorage.cpp
index 95bb42e..ef0750c 100644
--- a/core/jni/hwbinder/EphemeralStorage.cpp
+++ b/core/jni/hwbinder/EphemeralStorage.cpp
@@ -164,7 +164,7 @@
             }
 
             default:
-                CHECK(!"Should not be here");
+                CHECK(!"Should not be here") << "Item type: " << item.mType;
         }
     }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e65bfab..1eeffb9 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1709,6 +1709,7 @@
 
     <!-- @SystemApi Allows camera access by Headless System User 0 when device is running in
             HSUM Mode.
+         @FlaggedApi("com.android.internal.camera.flags.camera_hsum_permission")
     @hide -->
     <permission android:name="android.permission.CAMERA_HEADLESS_SYSTEM_USER"
         android:permissionGroup="android.permission-group.UNDEFINED"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d1143c4..a23201e 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -301,9 +301,7 @@
             granted to the system companion device manager service -->
         <flag name="companion" value="0x800000" />
         <!-- Additional flag from base permission type: this permission will be granted to the
-             retail demo app, as defined by the OEM.
-             This flag has been replaced by the retail demo role and is a no-op since Android V.
-          -->
+             retail demo app, as defined by the OEM. -->
         <flag name="retailDemo" value="0x1000000" />
         <!-- Additional flag from base permission type: this permission will be granted to the
              recents app. -->
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index eddd81e..53a6270 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -568,10 +568,6 @@
     <color name="side_fps_button_color">#00677E</color>
 
     <!-- Color for system bars -->
-    <color name="status_bar_compatible">@android:color/black</color>
-    <!-- This uses non-regular transparent intentionally. It is used to tell if the transparent
-         color is set by the framework or not. -->
-    <color name="status_bar_default">#00808080</color>
     <color name="navigation_bar_compatible">@android:color/black</color>
     <!-- This uses non-regular transparent intentionally. It is used to tell if the transparent
          color is set by the framework or not. -->
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 540967d..17bb86a 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -128,7 +128,7 @@
   </staging-public-group>
 
   <staging-public-group type="string" first-id="0x01ba0000">
-    <!-- @hide @SystemApi -->
+    <!-- @hide @SystemApi @FlaggedApi("android.permission.flags.retail_demo_role_enabled") -->
     <public name="config_defaultRetailDemo" />
   </staging-public-group>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b0a4c16..d12ef2b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3095,8 +3095,6 @@
   <java-symbol type="bool" name="config_navBarDefaultTransparent" />
   <java-symbol type="color" name="navigation_bar_default"/>
   <java-symbol type="color" name="navigation_bar_compatible"/>
-  <java-symbol type="color" name="status_bar_default"/>
-  <java-symbol type="color" name="status_bar_compatible"/>
 
   <!-- EditText suggestion popup. -->
   <java-symbol type="id" name="suggestionWindowContainer" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index d5d67ab..bdbf96b 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -190,7 +190,7 @@
         <item name="windowTranslucentStatus">false</item>
         <item name="windowTranslucentNavigation">false</item>
         <item name="windowDrawsSystemBarBackgrounds">false</item>
-        <item name="statusBarColor">@color/status_bar_default</item>
+        <item name="statusBarColor">@color/black</item>
         <item name="navigationBarColor">@color/navigation_bar_default</item>
         <item name="windowActionBarFullscreenDecorLayout">@layout/screen_action_bar</item>
         <item name="windowContentTransitions">false</item>
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 723c081..a796a0f 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -171,7 +171,8 @@
 
     @Test
     public void testRecycleStartActivityItem() {
-        testRecycle(() -> StartActivityItem.obtain(mActivityToken, ActivityOptions.makeBasic()));
+        testRecycle(() -> StartActivityItem.obtain(mActivityToken,
+                new ActivityOptions.SceneTransitionInfo()));
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index c0e2a49..3823033 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -255,9 +255,9 @@
             return LaunchActivityItem.obtain(mActivityToken, mIntent, mIdent, mInfo,
                     mCurConfig, mOverrideConfig, mDeviceId, mReferrer, mVoiceInteractor,
                     mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents,
-                    mActivityOptions, mIsForward, mProfilerInfo, mAssistToken,
-                    null /* activityClientController */, mShareableActivityToken,
-                    mLaunchedFromBubble, mTaskFragmentToken);
+                    mActivityOptions != null ? mActivityOptions.getSceneTransitionInfo() : null,
+                    mIsForward, mProfilerInfo, mAssistToken, null /* activityClientController */,
+                    mShareableActivityToken, mLaunchedFromBubble, mTaskFragmentToken);
         }
     }
 }
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 07921bf..952cdd9 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -262,7 +262,7 @@
     public void testStart() {
         // Write to parcel
         StartActivityItem item = StartActivityItem.obtain(mActivityToken,
-                ActivityOptions.makeBasic());
+                new ActivityOptions.SceneTransitionInfo());
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
diff --git a/data/keyboards/Android.bp b/data/keyboards/Android.bp
new file mode 100644
index 0000000..f15c153
--- /dev/null
+++ b/data/keyboards/Android.bp
@@ -0,0 +1,29 @@
+// Copyright 2010 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.
+
+genrule {
+    name: "validate_framework_keymaps",
+    srcs: [
+        "*.kl",
+        "*.kcm",
+        "*.idc",
+    ],
+    tools: ["validatekeymaps"],
+    out: ["stamp"],
+    cmd: "$(location validatekeymaps) -q $(in) " +
+        "&& touch $(out)",
+    dist: {
+        targets: ["droidcore"],
+    },
+}
diff --git a/data/keyboards/Android.mk b/data/keyboards/Android.mk
deleted file mode 100644
index 6ae8800..0000000
--- a/data/keyboards/Android.mk
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# This makefile performs build time validation of framework keymap files.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(LOCAL_PATH)/common.mk
-
-# Validate all key maps.
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := validate_framework_keymaps
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-intermediates := $(call intermediates-dir-for,ETC,$(LOCAL_MODULE),,COMMON)
-LOCAL_BUILT_MODULE := $(intermediates)/stamp
-
-validatekeymaps := $(HOST_OUT_EXECUTABLES)/validatekeymaps$(HOST_EXECUTABLE_SUFFIX)
-$(LOCAL_BUILT_MODULE): PRIVATE_VALIDATEKEYMAPS := $(validatekeymaps)
-$(LOCAL_BUILT_MODULE) : $(framework_keylayouts) $(framework_keycharmaps) $(framework_keyconfigs) | $(validatekeymaps)
-	$(hide) $(PRIVATE_VALIDATEKEYMAPS) -q $^
-	$(hide) mkdir -p $(dir $@) && touch $@
-
-# Run validatekeymaps uncondionally for platform build.
-droidcore : $(LOCAL_BUILT_MODULE)
-
-# Reset temp vars.
-validatekeymaps :=
-framework_keylayouts :=
-framework_keycharmaps :=
-framework_keyconfigs :=
diff --git a/data/keyboards/common.mk b/data/keyboards/common.mk
deleted file mode 100644
index d75b691..0000000
--- a/data/keyboards/common.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# This is the list of framework provided keylayouts and key character maps to include.
-# Used by Android.mk and keyboards.mk.
-
-framework_keylayouts := $(wildcard $(LOCAL_PATH)/*.kl)
-
-framework_keycharmaps := $(wildcard $(LOCAL_PATH)/*.kcm)
-
-framework_keyconfigs := $(wildcard $(LOCAL_PATH)/*.idc)
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 65597de..066f38b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -819,14 +819,20 @@
             Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId);
             return;
         }
+        // Checks if container should be updated before apply new parentInfo.
+        final boolean shouldUpdateContainer = taskContainer.shouldUpdateContainer(parentInfo);
         taskContainer.updateTaskFragmentParentInfo(parentInfo);
         if (!taskContainer.isVisible()) {
             // Don't update containers if the task is not visible. We only update containers when
             // parentInfo#isVisibleRequested is true.
             return;
         }
-        if (isInPictureInPicture(parentInfo.getConfiguration())) {
-            // No need to update presentation in PIP until the Task exit PIP.
+
+        // If the last direct activity of the host task is dismissed and the overlay container is
+        // the only taskFragment, the overlay container should also be dismissed.
+        dismissOverlayContainerIfNeeded(wct, taskContainer);
+
+        if (!shouldUpdateContainer) {
             return;
         }
         updateContainersInTask(wct, taskContainer);
@@ -1947,11 +1953,8 @@
     void updateOverlayContainer(@NonNull WindowContainerTransaction wct,
             @NonNull TaskFragmentContainer container) {
         final TaskContainer taskContainer = container.getTaskContainer();
-        // Dismiss the overlay container if it's the only container in the task and there's no
-        // direct activity in the parent task.
-        if (taskContainer.getTaskFragmentContainers().size() == 1
-                && !taskContainer.hasDirectActivity()) {
-            container.finish(false /* shouldFinishDependent */, mPresenter, wct, this);
+
+        if (dismissOverlayContainerIfNeeded(wct, taskContainer)) {
             return;
         }
 
@@ -1968,6 +1971,24 @@
         }
     }
 
+    /** Dismisses the overlay container in the {@code taskContainer} if needed. */
+    @GuardedBy("mLock")
+    private boolean dismissOverlayContainerIfNeeded(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskContainer taskContainer) {
+        final TaskFragmentContainer overlayContainer = taskContainer.getOverlayContainer();
+        if (overlayContainer == null) {
+            return false;
+        }
+        // Dismiss the overlay container if it's the only container in the task and there's no
+        // direct activity in the parent task.
+        if (taskContainer.getTaskFragmentContainers().size() == 1
+                && !taskContainer.hasDirectActivity()) {
+            mPresenter.cleanupContainer(wct, overlayContainer, false /* shouldFinishDependant */);
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Updates {@link SplitContainer} with the given {@link SplitAttributes} if the
      * {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 64ad4fa..71195b6 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -138,6 +138,21 @@
     }
 
     /**
+     * Returns {@code true} if the container should be updated with {@code info}.
+     */
+    boolean shouldUpdateContainer(@NonNull TaskFragmentParentInfo info) {
+        final Configuration configuration = info.getConfiguration();
+
+        return info.isVisible()
+                // No need to update presentation in PIP until the Task exit PIP.
+                && !isInPictureInPicture(configuration)
+                // If the task properties equals regardless of starting position, don't need to
+                // update the container.
+                && (mConfiguration.diffPublicOnly(configuration) != 0
+                || mDisplayId != info.getDisplayId());
+    }
+
+    /**
      * Returns the windowing mode for the TaskFragments below this Task, which should be split with
      * other TaskFragments.
      *
@@ -161,7 +176,11 @@
     }
 
     boolean isInPictureInPicture() {
-        return getWindowingMode() == WINDOWING_MODE_PINNED;
+        return isInPictureInPicture(mConfiguration);
+    }
+
+    private static boolean isInPictureInPicture(@NonNull Configuration configuration) {
+        return configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
     }
 
     boolean isInMultiWindow() {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index b52971a..6fe8e50 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -616,6 +616,9 @@
      * Removes all activities that belong to this process and finishes other containers/activities
      * configured to finish together.
      */
+    // Suppress GuardedBy warning because lint ask to mark this method as
+    // @GuardedBy(container.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
     @GuardedBy("mController.mLock")
     void finish(boolean shouldFinishDependent, @NonNull SplitPresenter presenter,
             @NonNull WindowContainerTransaction wct, @NonNull SplitController controller) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
index 396956e..6624c70 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TransactionManager.java
@@ -77,9 +77,11 @@
     @NonNull
     TransactionRecord startNewTransaction(@Nullable IBinder taskFragmentTransactionToken) {
         if (mCurrentTransaction != null) {
+            final TransactionRecord lastTransaction = mCurrentTransaction;
             mCurrentTransaction = null;
             throw new IllegalStateException(
-                    "The previous transaction has not been applied or aborted,");
+                    "The previous transaction:" + lastTransaction + " has not been applied or "
+                            + "aborted.");
         }
         mCurrentTransaction = new TransactionRecord(taskFragmentTransactionToken);
         return mCurrentTransaction;
@@ -199,5 +201,15 @@
                     ? mOriginType
                     : TASK_FRAGMENT_TRANSIT_CHANGE;
         }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return TransactionRecord.class.getSimpleName() + "{"
+                    + "token=" + mTaskFragmentTransactionToken
+                    + ", type=" + getTransactionTransitionType()
+                    + ", transaction=" + mTransaction
+                    + "}";
+        }
     }
 }
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 5ef6a52..bc92101 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -472,6 +472,29 @@
         verify(mSplitPresenter).applyActivityStackAttributes(any(), eq(container), eq(attrs));
     }
 
+    @Test
+    public void testOnTaskFragmentParentInfoChanged_positionOnlyChange_earlyReturn() {
+        final TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test");
+
+        final TaskContainer taskContainer = overlayContainer.getTaskContainer();
+        spyOn(taskContainer);
+        final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties();
+        final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo(
+                new Configuration(taskProperties.getConfiguration()), taskProperties.getDisplayId(),
+                true /* visible */, false /* hasDirectActivity */, null /* decorSurface */);
+        parentInfo.getConfiguration().windowConfiguration.getBounds().offset(10, 10);
+
+        mSplitController.onTaskFragmentParentInfoChanged(mTransaction, TASK_ID, parentInfo);
+
+        // The parent info must be applied to the task container
+        verify(taskContainer).updateTaskFragmentParentInfo(parentInfo);
+        verify(mSplitController, never()).updateContainer(any(), any());
+
+        assertWithMessage("The overlay container must still be dismissed even if "
+                + "#updateContainer is not called")
+                .that(taskContainer.getOverlayContainer()).isNull();
+    }
+
     /**
      * A simplified version of {@link SplitController.ActivityStartMonitor
      * #createOrUpdateOverlayTaskFragmentIfNeeded}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index 6213f62..8f04f12 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -63,6 +63,10 @@
         if ((touchX < mStartThresholdX && mSwipeEdge == BackEvent.EDGE_LEFT)
                 || (touchX > mStartThresholdX && mSwipeEdge == BackEvent.EDGE_RIGHT)) {
             mStartThresholdX = touchX;
+            if ((mSwipeEdge == BackEvent.EDGE_LEFT && mStartThresholdX < mInitTouchX)
+                    || (mSwipeEdge == BackEvent.EDGE_RIGHT && mStartThresholdX > mInitTouchX)) {
+                mInitTouchX = mStartThresholdX;
+            }
         }
         mLatestTouchX = touchX;
         mLatestTouchY = touchY;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index 34c015f..84f21f6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -330,7 +330,7 @@
                     continue;
                 }
                 if (isHide) {
-                    if (pending.mType == TRANSIT_TO_BACK) {
+                    if (pending != null && pending.mType == TRANSIT_TO_BACK) {
                         // TO_BACK is only used when setting the task view visibility immediately,
                         // so in that case we can also hide the surface immediately
                         startTransaction.hide(chg.getLeash());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt
index bf07dcc..6dbb1e2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.kt
@@ -170,6 +170,71 @@
         nonLinearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / nonLinearTarget)
     }
 
+    @Test
+    fun restartingGesture_resetsInitialTouchX_leftEdge() {
+        val linearTracker = linearTouchTracker()
+        linearTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0f, BackEvent.EDGE_LEFT)
+        var touchX = 100f
+        val velocityX = 0f
+        val velocityY = 0f
+
+        // assert that progress is increased when increasing touchX
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((touchX - INITIAL_X_LEFT_EDGE) / MAX_DISTANCE)
+
+        // assert that progress is reset to 0 when start location is updated
+        linearTracker.updateStartLocation()
+        linearTracker.assertProgress(0f)
+
+        // assert that progress remains 0 when touchX is decreased
+        touchX -= 50
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress(0f)
+
+        // assert that progress uses new minimal touchX for progress calculation
+        val newInitialTouchX = touchX
+        touchX += 100
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((touchX - newInitialTouchX) / MAX_DISTANCE)
+
+        // assert the same for triggerBack==true
+        linearTracker.triggerBack = true
+        linearTracker.assertProgress((touchX - newInitialTouchX) / MAX_DISTANCE)
+    }
+
+    @Test
+    fun restartingGesture_resetsInitialTouchX_rightEdge() {
+        val linearTracker = linearTouchTracker()
+        linearTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0f, BackEvent.EDGE_RIGHT)
+
+        var touchX = INITIAL_X_RIGHT_EDGE - 100f
+        val velocityX = 0f
+        val velocityY = 0f
+
+        // assert that progress is increased when decreasing touchX
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((INITIAL_X_RIGHT_EDGE - touchX) / MAX_DISTANCE)
+
+        // assert that progress is reset to 0 when start location is updated
+        linearTracker.updateStartLocation()
+        linearTracker.assertProgress(0f)
+
+        // assert that progress remains 0 when touchX is increased
+        touchX += 50
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress(0f)
+
+        // assert that progress uses new maximal touchX for progress calculation
+        val newInitialTouchX = touchX
+        touchX -= 100
+        linearTracker.update(touchX, 0f, velocityX, velocityY)
+        linearTracker.assertProgress((newInitialTouchX - touchX) / MAX_DISTANCE)
+
+        // assert the same for triggerBack==true
+        linearTracker.triggerBack = true
+        linearTracker.assertProgress((newInitialTouchX - touchX) / MAX_DISTANCE)
+    }
+
     companion object {
         private const val MAX_DISTANCE = 500f
         private const val LINEAR_DISTANCE = 400f
diff --git a/media/java/android/media/LoudnessCodecConfigurator.java b/media/java/android/media/LoudnessCodecConfigurator.java
index de9d87c0..aadd783 100644
--- a/media/java/android/media/LoudnessCodecConfigurator.java
+++ b/media/java/android/media/LoudnessCodecConfigurator.java
@@ -234,19 +234,21 @@
      * @param mediaCodec the codec to start receiving asynchronous loudness
      *                   updates. The codec has to be in a configured or started
      *                   state in order to add it for loudness updates.
-     * @throws IllegalArgumentException if the {@code mediaCodec} was not configured,
-     *                                  does not contain loudness metadata or if it
-     *                                  was already added before
+     * @throws IllegalArgumentException if the same {@code mediaCodec} was already
+     *                                  added before.
+     * @return {@code false} if the {@code mediaCodec} was not configured or does
+     *         not contain loudness metadata, {@code true} otherwise.
      */
     @FlaggedApi(FLAG_LOUDNESS_CONFIGURATOR_API)
-    public void addMediaCodec(@NonNull MediaCodec mediaCodec) {
+    public boolean addMediaCodec(@NonNull MediaCodec mediaCodec) {
         final MediaCodec mc = Objects.requireNonNull(mediaCodec,
                 "MediaCodec for addMediaCodec cannot be null");
         int piid = PLAYER_PIID_INVALID;
         final LoudnessCodecInfo mcInfo = getCodecInfo(mc);
 
         if (mcInfo == null) {
-            throw new IllegalArgumentException("Could not extract codec loudness information");
+            Log.v(TAG, "Could not extract codec loudness information");
+            return false;
         }
         synchronized (mConfiguratorLock) {
             final AtomicBoolean containsCodec = new AtomicBoolean(false);
@@ -271,6 +273,8 @@
         if (piid != PLAYER_PIID_INVALID) {
             mLcDispatcher.addLoudnessCodecInfo(piid, mediaCodec.hashCode(), mcInfo);
         }
+
+        return true;
     }
 
     /**
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index b7c97208..470a8ac 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -3401,13 +3401,15 @@
         }
 
         /**
-         * Set a harware graphic buffer to this queue request. Exactly one buffer must
+         * Set a hardware graphic buffer to this queue request. Exactly one buffer must
          * be set for a queue request before calling {@link #queue}.
          * <p>
          * Note: buffers should have format {@link HardwareBuffer#YCBCR_420_888},
          * a single layer, and an appropriate usage ({@link HardwareBuffer#USAGE_CPU_READ_OFTEN}
          * for software codecs and {@link HardwareBuffer#USAGE_VIDEO_ENCODE} for hardware)
-         * for codecs to recognize.  Codecs may throw exception if the buffer is not recognizable.
+         * for codecs to recognize. Format {@link ImageFormat#PRIVATE} together with
+         * usage {@link HardwareBuffer#USAGE_VIDEO_ENCODE} will also work for hardware codecs.
+         * Codecs may throw exception if the buffer is not recognizable.
          *
          * @param buffer The hardware graphic buffer object
          * @return this object
diff --git a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
index ce1004c..74e5612 100644
--- a/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
+++ b/media/tests/LoudnessCodecApiTest/src/com/android/loudnesscodecapitest/LoudnessCodecConfiguratorTest.java
@@ -19,6 +19,7 @@
 import static android.media.audio.Flags.FLAG_LOUDNESS_CONFIGURATOR_API;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -191,6 +192,18 @@
 
     @Test
     @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
+    public void addUnconfiguredMediaCodec_returnsFalse() throws Exception {
+        final MediaCodec mediaCodec = MediaCodec.createDecoderByType("audio/mpeg");
+
+        try {
+            assertFalse(mLcc.addMediaCodec(mediaCodec));
+        } finally {
+            mediaCodec.release();
+        }
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_LOUDNESS_CONFIGURATOR_API)
     public void setClearTrack_removeAllAudioServicePiidCodecs() throws Exception {
         final ArgumentCaptor<List> argument = ArgumentCaptor.forClass(List.class);
         final AudioTrack track = createAudioTrack();
diff --git a/packages/CredentialManager/res/drawable/more_horiz_24px.xml b/packages/CredentialManager/res/drawable/more_horiz_24px.xml
new file mode 100644
index 0000000..7b235f8
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/more_horiz_24px.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M240,560Q207,560 183.5,536.5Q160,513 160,480Q160,447 183.5,423.5Q207,400 240,400Q273,400 296.5,423.5Q320,447 320,480Q320,513 296.5,536.5Q273,560 240,560ZM480,560Q447,560 423.5,536.5Q400,513 400,480Q400,447 423.5,423.5Q447,400 480,400Q513,400 536.5,423.5Q560,447 560,480Q560,513 536.5,536.5Q513,560 480,560ZM720,560Q687,560 663.5,536.5Q640,513 640,480Q640,447 663.5,423.5Q687,400 720,400Q753,400 776.5,423.5Q800,447 800,480Q800,513 776.5,536.5Q753,560 720,560Z"/>
+</vector>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index dfa5735..8ac364e7 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -305,6 +305,14 @@
         var i = 0
         var datasetAdded = false
 
+        val duplicateDisplayNames: MutableMap<String, Boolean> = mutableMapOf()
+        providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach {
+            val credentialEntry = it.sortedCredentialEntryList.first()
+            credentialEntry.displayName?.let {displayName ->
+                val duplicateEntry = duplicateDisplayNames.contains(displayName)
+                duplicateDisplayNames[displayName] = duplicateEntry
+            }
+        }
         providerDisplayInfo.sortedUserNameToCredentialEntryList.forEach usernameLoop@{
             val primaryEntry = it.sortedCredentialEntryList.first()
             val pendingIntent = primaryEntry.pendingIntent
@@ -339,10 +347,14 @@
                 } else {
                     spec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
                 }
+                val displayName : String = primaryEntry.displayName ?: primaryEntry.userName
                 val sliceBuilder = InlineSuggestionUi
                         .newContentBuilder(pendingIntent)
-                        .setTitle(primaryEntry.userName)
+                        .setTitle(displayName)
                 sliceBuilder.setStartIcon(icon)
+                if (duplicateDisplayNames[displayName] == true) {
+                    sliceBuilder.setSubtitle(primaryEntry.userName)
+                }
                 inlinePresentation = InlinePresentation(
                         sliceBuilder.build().slice, spec, /* pinned= */ false)
             }
@@ -398,7 +410,7 @@
         val sliceBuilder = InlineSuggestionUi
                 .newContentBuilder(bottomSheetPendingIntent)
                 .setStartIcon(Icon.createWithResource(this,
-                        com.android.credentialmanager.R.drawable.ic_other_sign_in_24))
+                        com.android.credentialmanager.R.drawable.more_horiz_24px))
         val presentationBuilder = Presentations.Builder()
                 .setInlinePresentation(InlinePresentation(
                         sliceBuilder.build().slice, spec, /* pinned= */ true))
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
index 754437e..b5af845 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveActivity.java
@@ -76,7 +76,7 @@
         boolean hasRequestInstallPermission = Arrays.asList(getRequestedPermissions(callingPackage))
                 .contains(permission.REQUEST_INSTALL_PACKAGES);
         boolean hasInstallPermission = getBaseContext().checkPermission(permission.INSTALL_PACKAGES,
-                0 /* random value for pid */, callingUid) != PackageManager.PERMISSION_GRANTED;
+                0 /* random value for pid */, callingUid) == PackageManager.PERMISSION_GRANTED;
         if (!hasRequestInstallPermission && !hasInstallPermission) {
             Log.e(TAG, "Uid " + callingUid + " does not have "
                     + permission.REQUEST_INSTALL_PACKAGES + " or "
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java
index 6ccbc4c..42dd382 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveFragment.java
@@ -28,12 +28,13 @@
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         String appTitle = getArguments().getString(UnarchiveActivity.APP_TITLE);
+        String installerTitle = getArguments().getString(UnarchiveActivity.INSTALLER_TITLE);
 
         AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity());
 
         dialogBuilder.setTitle(
                 String.format(getContext().getString(R.string.unarchive_application_title),
-                        appTitle));
+                        appTitle, installerTitle));
         dialogBuilder.setMessage(R.string.unarchive_body_text);
 
         dialogBuilder.setPositiveButton(R.string.restore, this);
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index f4edb36..460a6f7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -24,7 +24,8 @@
 import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
 import com.android.settingslib.spa.gallery.card.CardPageProvider
 import com.android.settingslib.spa.gallery.chart.ChartPageProvider
-import com.android.settingslib.spa.gallery.dialog.AlertDialogPageProvider
+import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
+import com.android.settingslib.spa.gallery.dialog.NavDialogProvider
 import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
 import com.android.settingslib.spa.gallery.editor.SettingsExposedDropdownMenuBoxPageProvider
 import com.android.settingslib.spa.gallery.editor.SettingsExposedDropdownMenuCheckBoxProvider
@@ -91,7 +92,8 @@
                 ProgressBarPageProvider,
                 LoadingBarPageProvider,
                 ChartPageProvider,
-                AlertDialogPageProvider,
+                DialogMainPageProvider,
+                NavDialogProvider,
                 ItemListPageProvider,
                 ItemOperatePageProvider,
                 OperateListPageProvider,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlertDialogPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
similarity index 84%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlertDialogPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
index 1545a3e..4e3fcee 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/AlertDialogPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/DialogMainPageProvider.kt
@@ -28,10 +28,10 @@
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 
-private const val TITLE = "AlertDialogPage"
+private const val TITLE = "Category: Dialog"
 
-object AlertDialogPageProvider : SettingsPageProvider {
-    override val name = "AlertDialogPage"
+object DialogMainPageProvider : SettingsPageProvider {
+    override val name = "DialogMain"
     private val owner = createSettingsPage()
 
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> = listOf(
@@ -47,6 +47,12 @@
                 override val onClick = alertDialogPresenter::open
             })
         }.build(),
+        SettingsEntryBuilder.create("NavDialog", owner).setUiLayoutFn {
+            Preference(object : PreferenceModel {
+                override val title = "Navigate to Dialog"
+                override val onClick = navigator(route = NavDialogProvider.name)
+            })
+        }.build(),
     )
 
     fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/NavDialogProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/NavDialogProvider.kt
new file mode 100644
index 0000000..6f79911
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/dialog/NavDialogProvider.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.dialog
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.widget.dialog.SettingsDialogCard
+
+object NavDialogProvider : SettingsPageProvider {
+    override val name = "NavDialog"
+    override val navType = SettingsPageProvider.NavType.Dialog
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        SettingsDialogCard("Example Nav Dialog") {}
+    }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
index 6a2e598..1f028d5 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
@@ -30,7 +30,7 @@
 import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
 import com.android.settingslib.spa.gallery.card.CardPageProvider
 import com.android.settingslib.spa.gallery.chart.ChartPageProvider
-import com.android.settingslib.spa.gallery.dialog.AlertDialogPageProvider
+import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider
 import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
 import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
 import com.android.settingslib.spa.gallery.page.ArgumentPageModel
@@ -71,7 +71,7 @@
             ProgressBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             LoadingBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             ChartPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
-            AlertDialogPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            DialogMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             EditorMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             CardPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             CopyablePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 9f8c868..5605485 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -22,7 +22,6 @@
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.annotation.VisibleForTesting
-import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.LaunchedEffect
@@ -30,15 +29,19 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.core.view.WindowCompat
+import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavGraph.Companion.findStartDestination
+import androidx.navigation.NavGraphBuilder
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
+import androidx.navigation.compose.dialog
 import androidx.navigation.compose.rememberNavController
 import com.android.settingslib.spa.R
 import com.android.settingslib.spa.framework.common.LogCategory
 import com.android.settingslib.spa.framework.common.NullPageProvider
 import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProvider.NavType
 import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.common.createSettingsPage
@@ -127,27 +130,31 @@
     allProvider: Collection<SettingsPageProvider>,
     content: @Composable (SettingsPage) -> Unit,
 ) {
-    // TODO(b/298520326): Remove Box after the issue is fixed.
-    // Wrap the top level node into a Box to workaround an issue of Compose 1.6.0-alpha03.
-    Box {
-        NavHost(
-            navController = navController,
-            startDestination = NullPageProvider.name,
-        ) {
-            composable(NullPageProvider.name) {}
-            for (spp in allProvider) {
-                animatedComposable(
-                    route = spp.name + spp.parameter.navRoute(),
-                    arguments = spp.parameter,
-                ) { navBackStackEntry ->
-                    val page = remember { spp.createSettingsPage(navBackStackEntry.arguments) }
-                    content(page)
-                }
+    NavHost(
+        navController = navController,
+        startDestination = NullPageProvider.name,
+    ) {
+        composable(NullPageProvider.name) {}
+        for (spp in allProvider) {
+            destination(spp) { navBackStackEntry ->
+                val page = remember { spp.createSettingsPage(navBackStackEntry.arguments) }
+                content(page)
             }
         }
     }
 }
 
+private fun NavGraphBuilder.destination(
+    spp: SettingsPageProvider,
+    content: @Composable (NavBackStackEntry) -> Unit,
+) {
+    val route = spp.name + spp.parameter.navRoute()
+    when (spp.navType) {
+        NavType.Page -> animatedComposable(route, spp.parameter) { content(it) }
+        NavType.Dialog -> dialog(route, spp.parameter) { content(it) }
+    }
+}
+
 @Composable
 private fun NavControllerWrapperImpl.InitialDestination(
     initialIntent: Intent?,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 18f964e..81bee5e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -34,6 +34,14 @@
     /** The page provider name, needs to be *unique* and *stable*. */
     val name: String
 
+    enum class NavType {
+        Page,
+        Dialog,
+    }
+
+    val navType: NavType
+        get() = NavType.Page
+
     /** The display name of this page provider, for better readability. */
     val displayName: String
         get() = name
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt
index 8b172da..f08e740 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsDialog.kt
@@ -19,10 +19,13 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.AlertDialogDefaults
 import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.window.Dialog
+import androidx.navigation.compose.NavHost
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsShape
 import com.android.settingslib.spa.widget.ui.SettingsTitle
@@ -34,13 +37,27 @@
     content: @Composable () -> Unit,
 ) {
     Dialog(onDismissRequest = onDismissRequest) {
-        Card(shape = SettingsShape.CornerExtraLarge) {
-            Column(modifier = Modifier.padding(vertical = SettingsDimension.itemPaddingAround)) {
-                Box(modifier = Modifier.padding(SettingsDimension.dialogItemPadding)) {
-                    SettingsTitle(title = title, useMediumWeight = true)
-                }
-                content()
+        SettingsDialogCard(title, content)
+    }
+}
+
+/**
+ * Card for dialog, suitable for independent dialog in the [NavHost].
+ */
+@Composable
+fun SettingsDialogCard(
+    title: String,
+    content: @Composable () -> Unit,
+) {
+    Card(
+        shape = SettingsShape.CornerExtraLarge,
+        colors = CardDefaults.cardColors(containerColor = AlertDialogDefaults.containerColor),
+    ) {
+        Column(modifier = Modifier.padding(vertical = SettingsDimension.itemPaddingAround)) {
+            Box(modifier = Modifier.padding(SettingsDimension.dialogItemPadding)) {
+                SettingsTitle(title = title, useMediumWeight = true)
             }
+            content()
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
index 92d3411..8cbd964 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.hasText
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onAllNodesWithText
 import androidx.compose.ui.test.onNodeWithText
@@ -29,12 +30,14 @@
 import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.tests.testutils.SppDialog
 import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
 import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest
 import com.android.settingslib.spa.tests.testutils.SppDisabled
 import com.android.settingslib.spa.tests.testutils.SppHome
 import com.android.settingslib.spa.testutils.waitUntil
-import com.google.common.truth.Truth
+import com.android.settingslib.spa.testutils.waitUntilExists
+import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -106,11 +109,31 @@
         composeTestRule.onNodeWithText(sppDisabled.getTitle(null)).assertDoesNotExist()
         spaLogger.verifyPageEvent(pageDisabled.id, 0, 0)
     }
+
+    @Test
+    fun browseContent_dialog() {
+        val spaEnvironment = SpaEnvironmentForTest(
+            context = context,
+            rootPages = listOf(SppHome.createSettingsPage()),
+            logger = spaLogger,
+        )
+        SpaEnvironmentFactory.reset(spaEnvironment)
+        val sppRepository by spaEnvironment.pageProviderRepository
+
+        composeTestRule.setContent {
+            BrowseContent(
+                sppRepository = sppRepository,
+                isPageEnabled = SettingsPage::isEnabled,
+                initialIntent = null,
+            )
+        }
+        composeTestRule.onNodeWithText(SppDialog.name).performClick()
+
+        composeTestRule.waitUntilExists(hasText(SppDialog.CONTENT))
+    }
 }
 
 private fun SpaLoggerForTest.verifyPageEvent(id: String, entryCount: Int, leaveCount: Int) {
-    Truth.assertThat(getEventCount(id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
-        .isEqualTo(entryCount)
-    Truth.assertThat(getEventCount(id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
-        .isEqualTo(leaveCount)
+    assertThat(getEventCount(id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK)).isEqualTo(entryCount)
+    assertThat(getEventCount(id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK)).isEqualTo(leaveCount)
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
index b139f28..0a1c05f 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
@@ -22,6 +22,7 @@
 import com.android.settingslib.spa.framework.util.genEntryId
 import com.android.settingslib.spa.framework.util.genPageId
 import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SppDialog
 import com.android.settingslib.spa.tests.testutils.SppHome
 import com.android.settingslib.spa.tests.testutils.SppLayer1
 import com.android.settingslib.spa.tests.testutils.SppLayer2
@@ -39,26 +40,21 @@
     @Test
     fun testGetPageWithEntry() {
         val pageWithEntry = entryRepository.getAllPageWithEntry()
-        assertThat(pageWithEntry.size).isEqualTo(3)
-        assertThat(
-            entryRepository.getPageWithEntry(genPageId("SppHome"))
-                ?.entries?.size
-        ).isEqualTo(1)
-        assertThat(
-            entryRepository.getPageWithEntry(genPageId("SppLayer1"))
-                ?.entries?.size
-        ).isEqualTo(3)
-        assertThat(
-            entryRepository.getPageWithEntry(genPageId("SppLayer2"))
-                ?.entries?.size
-        ).isEqualTo(2)
+
+        assertThat(pageWithEntry).hasSize(4)
+        assertThat(entryRepository.getPageWithEntry(genPageId("SppHome"))?.entries)
+            .hasSize(2)
+        assertThat(entryRepository.getPageWithEntry(genPageId("SppLayer1"))?.entries)
+            .hasSize(3)
+        assertThat(entryRepository.getPageWithEntry(genPageId("SppLayer2"))?.entries)
+            .hasSize(2)
         assertThat(entryRepository.getPageWithEntry(genPageId("SppWithParam"))).isNull()
     }
 
     @Test
     fun testGetEntry() {
         val entry = entryRepository.getAllEntries()
-        assertThat(entry.size).isEqualTo(7)
+        assertThat(entry).hasSize(8)
         assertThat(
             entryRepository.getEntry(
                 genEntryId(
@@ -91,6 +87,16 @@
         ).isNotNull()
         assertThat(
             entryRepository.getEntry(
+                genEntryId(
+                    "INJECT",
+                    SppDialog.createSettingsPage(),
+                    SppHome.createSettingsPage(),
+                    SppDialog.createSettingsPage(),
+                )
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
                 genEntryId("Layer1Entry1", SppLayer1.createSettingsPage())
             )
         ).isNotNull()
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
index 8576573..169c541 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
@@ -25,39 +25,55 @@
 @RunWith(AndroidJUnit4::class)
 class SettingsPageProviderRepositoryTest {
     @Test
-    fun getStartPageTest() {
-        val sppRepoEmpty = SettingsPageProviderRepository(emptyList(), emptyList())
+    fun rootPages_empty() {
+        val sppRepoEmpty = SettingsPageProviderRepository(emptyList())
+
         assertThat(sppRepoEmpty.getDefaultStartPage()).isEqualTo("")
         assertThat(sppRepoEmpty.getAllRootPages()).isEmpty()
-
-        val nullPage = NullPageProvider.createSettingsPage()
-        val sppRepoNull =
-            SettingsPageProviderRepository(emptyList(), listOf(nullPage))
-        assertThat(sppRepoNull.getDefaultStartPage()).isEqualTo("NULL")
-        assertThat(sppRepoNull.getAllRootPages()).contains(nullPage)
-
-        val rootPage1 = createSettingsPage(sppName = "Spp1", displayName = "Spp1")
-        val rootPage2 = createSettingsPage(sppName = "Spp2", displayName = "Spp2")
-        val sppRepo = SettingsPageProviderRepository(emptyList(), listOf(rootPage1, rootPage2))
-        val allRoots = sppRepo.getAllRootPages()
-        assertThat(sppRepo.getDefaultStartPage()).isEqualTo("Spp1")
-        assertThat(allRoots.size).isEqualTo(2)
-        assertThat(allRoots).contains(rootPage1)
-        assertThat(allRoots).contains(rootPage2)
     }
 
     @Test
-    fun getProviderTest() {
-        val sppRepoEmpty = SettingsPageProviderRepository(emptyList(), emptyList())
+    fun rootPages_single() {
+        val nullPage = NullPageProvider.createSettingsPage()
+
+        val sppRepoNull = SettingsPageProviderRepository(
+            allPageProviders = emptyList(),
+            rootPages = listOf(nullPage),
+        )
+
+        assertThat(sppRepoNull.getDefaultStartPage()).isEqualTo("NULL")
+        assertThat(sppRepoNull.getAllRootPages()).containsExactly(nullPage)
+    }
+
+    @Test
+    fun rootPages_twoPages() {
+        val rootPage1 = createSettingsPage(sppName = "Spp1", displayName = "Spp1")
+        val rootPage2 = createSettingsPage(sppName = "Spp2", displayName = "Spp2")
+
+        val sppRepo = SettingsPageProviderRepository(
+            allPageProviders = emptyList(),
+            rootPages = listOf(rootPage1, rootPage2),
+        )
+
+        assertThat(sppRepo.getDefaultStartPage()).isEqualTo("Spp1")
+        assertThat(sppRepo.getAllRootPages()).containsExactly(rootPage1, rootPage2)
+    }
+
+    @Test
+    fun getProviderOrNull_empty() {
+        val sppRepoEmpty = SettingsPageProviderRepository(emptyList())
         assertThat(sppRepoEmpty.getAllProviders()).isEmpty()
         assertThat(sppRepoEmpty.getProviderOrNull("Spp")).isNull()
+    }
 
+    @Test
+    fun getProviderOrNull_single() {
         val sppRepo = SettingsPageProviderRepository(listOf(
             object : SettingsPageProvider {
                 override val name = "Spp"
             }
-        ), emptyList())
-        assertThat(sppRepo.getAllProviders().size).isEqualTo(1)
+        ))
+        assertThat(sppRepo.getAllProviders()).hasSize(1)
         assertThat(sppRepo.getProviderOrNull("Spp")).isNotNull()
         assertThat(sppRepo.getProviderOrNull("SppUnknown")).isNull()
     }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
index 2755b4e..22a5ca3 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
@@ -21,6 +21,8 @@
 import android.content.Context
 import android.content.Intent
 import android.os.Bundle
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
 import com.android.settingslib.spa.framework.BrowseActivity
@@ -88,6 +90,7 @@
         val owner = this.createSettingsPage()
         return listOf(
             SppLayer1.buildInject().setLink(fromPage = owner).build(),
+            SppDialog.buildInject().setLink(fromPage = owner).build(),
         )
     }
 }
@@ -160,6 +163,21 @@
     }
 }
 
+object SppDialog : SettingsPageProvider {
+    override val name = "SppDialog"
+    override val navType = SettingsPageProvider.NavType.Dialog
+
+    const val CONTENT = "SppDialog Content"
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        Text(CONTENT)
+    }
+
+    fun buildInject() = SettingsEntryBuilder.createInject(this.createSettingsPage())
+        .setMacro { SimplePreferenceMacro(title = name, clickRoute = name) }
+}
+
 object SppForSearch : SettingsPageProvider {
     override val name = "SppForSearch"
 
@@ -223,6 +241,7 @@
                         navArgument("rt_param") { type = NavType.StringType },
                     )
                 },
+                SppDialog,
             ),
             rootPages
         )
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index 9560b8d..cdb8740 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -19,6 +19,7 @@
 
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
@@ -42,18 +43,16 @@
     BluetoothMediaDevice(
             Context context,
             CachedBluetoothDevice device,
-            MediaRoute2Info info,
-            String packageName) {
-        this(context, device, info, packageName, null);
+            MediaRoute2Info info) {
+        this(context, device, info, null);
     }
 
     BluetoothMediaDevice(
             Context context,
             CachedBluetoothDevice device,
             MediaRoute2Info info,
-            String packageName,
             RouteListingPreference.Item item) {
-        super(context, info, packageName, item);
+        super(context, info, item);
         mCachedDevice = device;
         mAudioManager = context.getSystemService(AudioManager.class);
         initDeviceRecord();
@@ -100,7 +99,12 @@
 
     @Override
     public String getId() {
-        return MediaDeviceUtils.getId(mCachedDevice);
+        if (mCachedDevice.isHearingAidDevice()) {
+            if (mCachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
+                return Long.toString(mCachedDevice.getHiSyncId());
+            }
+        }
+        return mCachedDevice.getAddress();
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ComplexMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/ComplexMediaDevice.java
index 4e0ebd1..338fb87 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ComplexMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ComplexMediaDevice.java
@@ -34,9 +34,8 @@
     ComplexMediaDevice(
             Context context,
             MediaRoute2Info info,
-            String packageName,
             RouteListingPreference.Item item) {
-        super(context, info, packageName, item);
+        super(context, info, item);
     }
 
     // MediaRoute2Info.getName was made public on API 34, but exists since API 30.
@@ -63,7 +62,7 @@
 
     @Override
     public String getId() {
-        return MediaDeviceUtils.getId(mRouteInfo);
+        return mRouteInfo.getId();
     }
 
     public boolean isConnected() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index 012cbc0..1347dd1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -45,14 +45,13 @@
     InfoMediaDevice(
             Context context,
             MediaRoute2Info info,
-            String packageName,
             RouteListingPreference.Item item) {
-        super(context, info, packageName, item);
+        super(context, info, item);
         initDeviceRecord();
     }
 
-    InfoMediaDevice(Context context, MediaRoute2Info info, String packageName) {
-        this(context, info, packageName, null);
+    InfoMediaDevice(Context context, MediaRoute2Info info) {
+        this(context, info, null);
     }
 
     @Override
@@ -118,7 +117,7 @@
 
     @Override
     public String getId() {
-        return MediaDeviceUtils.getId(mRouteInfo);
+        return mRouteInfo.getId();
     }
 
     public boolean isConnected() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index e5fce5b..581c7de 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -383,7 +383,7 @@
         for (MediaRoute2Info route : getSelectableRoutes(info)) {
             deviceList.add(
                     new InfoMediaDevice(
-                            mContext, route, mPackageName, mPreferenceItemMap.get(route.getId())));
+                            mContext, route, mPreferenceItemMap.get(route.getId())));
         }
         return deviceList;
     }
@@ -410,7 +410,7 @@
         for (MediaRoute2Info route : getDeselectableRoutes(info)) {
             deviceList.add(
                     new InfoMediaDevice(
-                            mContext, route, mPackageName, mPreferenceItemMap.get(route.getId())));
+                            mContext, route, mPreferenceItemMap.get(route.getId())));
             Log.d(TAG, route.getName() + " is deselectable for " + mPackageName);
         }
         return deviceList;
@@ -434,7 +434,7 @@
         for (MediaRoute2Info route : getSelectedRoutes(info)) {
             deviceList.add(
                     new InfoMediaDevice(
-                            mContext, route, mPackageName, mPreferenceItemMap.get(route.getId())));
+                            mContext, route, mPreferenceItemMap.get(route.getId())));
         }
         return deviceList;
     }
@@ -633,7 +633,6 @@
                         new InfoMediaDevice(
                                 mContext,
                                 route,
-                                mPackageName,
                                 mPreferenceItemMap.get(route.getId()));
                 break;
             case TYPE_BUILTIN_SPEAKER:
@@ -650,7 +649,6 @@
                         new PhoneMediaDevice(
                                 mContext,
                                 route,
-                                mPackageName,
                                 mPreferenceItemMap.getOrDefault(route.getId(), null));
                 break;
             case TYPE_HEARING_AID:
@@ -666,7 +664,6 @@
                                     mContext,
                                     cachedDevice,
                                     route,
-                                    mPackageName,
                                     mPreferenceItemMap.getOrDefault(route.getId(), null));
                 }
                 break;
@@ -675,7 +672,6 @@
                         new ComplexMediaDevice(
                                 mContext,
                                 route,
-                                mPackageName,
                                 mPreferenceItemMap.get(route.getId()));
                 break;
             default:
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index dbc3bf7..ebcca42 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -567,7 +567,7 @@
                 final CachedBluetoothDevice cachedDevice =
                         cachedDeviceManager.findDevice(device);
                 if (isBondedMediaDevice(cachedDevice) && isMutingExpectedDevice(cachedDevice)) {
-                    return new BluetoothMediaDevice(mContext, cachedDevice, null, mPackageName);
+                    return new BluetoothMediaDevice(mContext, cachedDevice, null);
                 }
             }
             return null;
@@ -614,7 +614,7 @@
             mDisconnectedMediaDevices.clear();
             for (CachedBluetoothDevice cachedDevice : cachedBluetoothDeviceList) {
                 final MediaDevice mediaDevice =
-                        new BluetoothMediaDevice(mContext, cachedDevice, null, mPackageName);
+                        new BluetoothMediaDevice(mContext, cachedDevice, null);
                 if (!mMediaDevices.contains(mediaDevice)) {
                     cachedDevice.registerCallback(mDeviceAttributeChangeCallback);
                     mDisconnectedMediaDevices.add(mediaDevice);
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index c8e4c0c..f2d9d14 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -121,16 +121,13 @@
     protected final Context mContext;
     protected final MediaRoute2Info mRouteInfo;
     protected final RouteListingPreference.Item mItem;
-    protected final String mPackageName;
 
     MediaDevice(
             Context context,
             MediaRoute2Info info,
-            String packageName,
             RouteListingPreference.Item item) {
         mContext = context;
         mRouteInfo = info;
-        mPackageName = packageName;
         mItem = item;
         setType(info);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java
deleted file mode 100644
index b3a52b9..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDeviceUtils.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.settingslib.media;
-
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHearingAid;
-import android.media.MediaRoute2Info;
-
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-
-/**
- * MediaDeviceUtils provides utility function for MediaDevice
- */
-public class MediaDeviceUtils {
-    /**
-     * Use CachedBluetoothDevice address to represent unique id
-     *
-     * @param cachedDevice the CachedBluetoothDevice
-     * @return CachedBluetoothDevice address
-     */
-    public static String getId(CachedBluetoothDevice cachedDevice) {
-        if (cachedDevice.isHearingAidDevice()) {
-            if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
-                return Long.toString(cachedDevice.getHiSyncId());
-            }
-        }
-        return cachedDevice.getAddress();
-    }
-
-    /**
-     * Use BluetoothDevice address to represent unique id
-     *
-     * @param bluetoothDevice the BluetoothDevice
-     * @return BluetoothDevice address
-     */
-    public static String getId(BluetoothDevice bluetoothDevice) {
-        return bluetoothDevice.getAddress();
-    }
-
-    /**
-     * Use MediaRoute2Info id to represent unique id
-     *
-     * @param route the MediaRoute2Info
-     * @return MediaRoute2Info id
-     */
-    public static String getId(MediaRoute2Info route) {
-        return route.getId();
-    }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 0676ce5..d6f1eab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -117,16 +117,15 @@
         return name.toString();
     }
 
-    PhoneMediaDevice(Context context, MediaRoute2Info info, String packageName) {
-        this(context, info, packageName, null);
+    PhoneMediaDevice(Context context, MediaRoute2Info info) {
+        this(context, info, null);
     }
 
     PhoneMediaDevice(
             Context context,
             MediaRoute2Info info,
-            String packageName,
             RouteListingPreference.Item item) {
-        super(context, info, packageName, item);
+        super(context, info, item);
         mDeviceIconUtil = new DeviceIconUtil(mContext);
         initDeviceRecord();
     }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java
index f50802a..7061742 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaDeviceTest.java
@@ -39,6 +39,8 @@
 @RunWith(RobolectricTestRunner.class)
 public class BluetoothMediaDeviceTest {
 
+    private static final String TEST_ADDRESS = "11:22:33:44:55:66";
+
     @Mock
     private CachedBluetoothDevice mDevice;
 
@@ -54,7 +56,7 @@
         when(mDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
         when(mDevice.isActiveDevice(BluetoothProfile.LE_AUDIO)).thenReturn(true);
 
-        mBluetoothMediaDevice = new BluetoothMediaDevice(mContext, mDevice, null, null, null);
+        mBluetoothMediaDevice = new BluetoothMediaDevice(mContext, mDevice, null, null);
     }
 
     @Test
@@ -111,4 +113,10 @@
 
         assertThat(mBluetoothMediaDevice.getIcon() instanceof BitmapDrawable).isFalse();
     }
+
+    @Test
+    public void getId_returnsCachedBluetoothDeviceAddress() {
+        when(mDevice.getAddress()).thenReturn(TEST_ADDRESS);
+        assertThat(mBluetoothMediaDevice.getId()).isEqualTo(TEST_ADDRESS);
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
index a072c17..0665308 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaDeviceTest.java
@@ -65,7 +65,7 @@
         MockitoAnnotations.initMocks(this);
         mContext = RuntimeEnvironment.application;
 
-        mInfoMediaDevice = new InfoMediaDevice(mContext, mRouteInfo, TEST_PACKAGE_NAME);
+        mInfoMediaDevice = new InfoMediaDevice(mContext, mRouteInfo);
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 2252b69..f0330c4 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -547,7 +547,7 @@
     @Test
     public void connectDeviceWithoutPackageName_noSession_returnFalse() {
         final MediaRoute2Info info = mock(MediaRoute2Info.class);
-        final MediaDevice device = new InfoMediaDevice(mContext, info, TEST_PACKAGE_NAME);
+        final MediaDevice device = new InfoMediaDevice(mContext, info);
 
         final List<RoutingSessionInfo> infos = new ArrayList<>();
 
@@ -623,7 +623,7 @@
         routingSessionInfos.add(info);
 
         final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
-        final MediaDevice device = new InfoMediaDevice(mContext, route2Info, TEST_PACKAGE_NAME);
+        final MediaDevice device = new InfoMediaDevice(mContext, route2Info);
 
         final List<String> list = new ArrayList<>();
         list.add(TEST_ID);
@@ -644,7 +644,7 @@
         routingSessionInfos.add(info);
 
         final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
-        final MediaDevice device = new InfoMediaDevice(mContext, route2Info, TEST_PACKAGE_NAME);
+        final MediaDevice device = new InfoMediaDevice(mContext, route2Info);
 
         final List<String> list = new ArrayList<>();
         list.add("fake_id");
@@ -674,7 +674,7 @@
         routingSessionInfos.add(info);
 
         final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
-        final MediaDevice device = new InfoMediaDevice(mContext, route2Info, TEST_PACKAGE_NAME);
+        final MediaDevice device = new InfoMediaDevice(mContext, route2Info);
 
         final List<String> list = new ArrayList<>();
         list.add(TEST_ID);
@@ -695,7 +695,7 @@
         routingSessionInfos.add(info);
 
         final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
-        final MediaDevice device = new InfoMediaDevice(mContext, route2Info, TEST_PACKAGE_NAME);
+        final MediaDevice device = new InfoMediaDevice(mContext, route2Info);
 
         final List<String> list = new ArrayList<>();
         list.add("fake_id");
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 926b41a..999e8d5 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -116,8 +116,8 @@
         when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
         when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
 
-        mInfoMediaDevice1 = spy(new InfoMediaDevice(mContext, mRouteInfo1, TEST_PACKAGE_NAME));
-        mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2, TEST_PACKAGE_NAME);
+        mInfoMediaDevice1 = spy(new InfoMediaDevice(mContext, mRouteInfo1));
+        mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2);
         mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
                 mInfoMediaManager, "com.test.packagename");
         mLocalMediaManager.mAudioManager = mAudioManager;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index 18055d9..098ab16 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -171,17 +171,17 @@
 
         mBluetoothMediaDevice1 =
                 new BluetoothMediaDevice(
-                        mContext, mCachedDevice1, mBluetoothRouteInfo1, TEST_PACKAGE_NAME);
+                        mContext, mCachedDevice1, mBluetoothRouteInfo1);
         mBluetoothMediaDevice2 =
                 new BluetoothMediaDevice(
-                        mContext, mCachedDevice2, mBluetoothRouteInfo2, TEST_PACKAGE_NAME);
+                        mContext, mCachedDevice2, mBluetoothRouteInfo2);
         mBluetoothMediaDevice3 =
                 new BluetoothMediaDevice(
-                        mContext, mCachedDevice3, mBluetoothRouteInfo3, TEST_PACKAGE_NAME);
-        mInfoMediaDevice1 = new InfoMediaDevice(mContext, mRouteInfo1, TEST_PACKAGE_NAME);
-        mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2, TEST_PACKAGE_NAME);
-        mInfoMediaDevice3 = new InfoMediaDevice(mContext, mRouteInfo3, TEST_PACKAGE_NAME);
-        mPhoneMediaDevice = new PhoneMediaDevice(mContext, mPhoneRouteInfo, TEST_PACKAGE_NAME);
+                        mContext, mCachedDevice3, mBluetoothRouteInfo3);
+        mInfoMediaDevice1 = new InfoMediaDevice(mContext, mRouteInfo1);
+        mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2);
+        mInfoMediaDevice3 = new InfoMediaDevice(mContext, mRouteInfo3);
+        mPhoneMediaDevice = new PhoneMediaDevice(mContext, mPhoneRouteInfo);
     }
 
     @Test
@@ -316,7 +316,7 @@
         when(phoneRouteInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES);
 
         final PhoneMediaDevice phoneMediaDevice =
-                new PhoneMediaDevice(mContext, phoneRouteInfo, TEST_PACKAGE_NAME);
+                new PhoneMediaDevice(mContext, phoneRouteInfo);
 
         mMediaDevices.add(mBluetoothMediaDevice1);
         mMediaDevices.add(phoneMediaDevice);
@@ -332,7 +332,7 @@
         when(phoneRouteInfo.getType()).thenReturn(TYPE_WIRED_HEADPHONES);
 
         final PhoneMediaDevice phoneMediaDevice =
-                new PhoneMediaDevice(mContext, phoneRouteInfo, TEST_PACKAGE_NAME);
+                new PhoneMediaDevice(mContext, phoneRouteInfo);
 
         mMediaDevices.add(mInfoMediaDevice1);
         mMediaDevices.add(phoneMediaDevice);
@@ -483,7 +483,7 @@
     public void getFeatures_noRouteInfo_returnEmptyList() {
         mBluetoothMediaDevice1 =
                 new BluetoothMediaDevice(
-                        mContext, mCachedDevice1, null /* MediaRoute2Info */, TEST_PACKAGE_NAME);
+                        mContext, mCachedDevice1, /* MediaRoute2Info */ null);
 
         assertThat(mBluetoothMediaDevice1.getFeatures().size()).isEqualTo(0);
     }
@@ -498,10 +498,9 @@
                         mContext,
                         mCachedDevice1,
                         null /* MediaRoute2Info */,
-                        TEST_PACKAGE_NAME,
                         mItem);
         mPhoneMediaDevice =
-                new PhoneMediaDevice(mContext, mPhoneRouteInfo, TEST_PACKAGE_NAME, mItem);
+                new PhoneMediaDevice(mContext, mPhoneRouteInfo, mItem);
 
         assertThat(mBluetoothMediaDevice1.getSelectionBehavior()).isEqualTo(
                 SELECTION_BEHAVIOR_TRANSFER);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceUtilsTest.java
deleted file mode 100644
index 30a6ad2..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceUtilsTest.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.media;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-
-import android.bluetooth.BluetoothDevice;
-import android.media.MediaRoute2Info;
-
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-
-@RunWith(RobolectricTestRunner.class)
-public class MediaDeviceUtilsTest {
-
-    private static final String TEST_ADDRESS = "11:22:33:44:55:66";
-    private static final String TEST_ROUTE_ID = "test_route_id";
-
-    @Mock
-    private CachedBluetoothDevice mCachedDevice;
-    @Mock
-    private BluetoothDevice mBluetoothDevice;
-    @Mock
-    private MediaRoute2Info mRouteInfo;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void getId_returnCachedBluetoothDeviceAddress() {
-        when(mCachedDevice.getAddress()).thenReturn(TEST_ADDRESS);
-
-        final String id = MediaDeviceUtils.getId(mCachedDevice);
-
-        assertThat(id).isEqualTo(TEST_ADDRESS);
-    }
-
-    @Test
-    public void getId_returnBluetoothDeviceAddress() {
-        when(mBluetoothDevice.getAddress()).thenReturn(TEST_ADDRESS);
-
-        final String id = MediaDeviceUtils.getId(mBluetoothDevice);
-
-        assertThat(id).isEqualTo(TEST_ADDRESS);
-    }
-
-    @Test
-    public void getId_returnRouteInfoId() {
-        when(mRouteInfo.getId()).thenReturn(TEST_ROUTE_ID);
-
-        final String id = MediaDeviceUtils.getId(mRouteInfo);
-
-        assertThat(id).isEqualTo(TEST_ROUTE_ID);
-    }
-}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index d6e8d26..2e39adc 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -69,6 +69,7 @@
         Settings.Global.PRIVATE_DNS_SPECIFIER,
         Settings.Global.SOFT_AP_TIMEOUT_ENABLED,
         Settings.Global.ZEN_DURATION,
+        Settings.Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE_USER_PREFERENCE,
         Settings.Global.REVERSE_CHARGING_AUTO_ON,
         Settings.Global.CHARGING_VIBRATION_ENABLED,
         Settings.Global.AWARE_ALLOWED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 59c3cd3..e7d7bb0 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -19,93 +19,105 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.provider.Settings;
 
+import com.android.server.display.feature.flags.Flags;
+
+import java.util.ArrayList;
+import java.util.List;
+
 /** Information about the system settings to back up */
 public class SystemSettings {
 
     /**
-     * Settings to backup.
+     * Settings to back up.
      *
      * NOTE: Settings are backed up and restored in the order they appear
      *       in this array. If you have one setting depending on another,
      *       make sure that they are ordered appropriately.
      */
     @UnsupportedAppUsage
-    public static final String[] SETTINGS_TO_BACKUP = {
-        Settings.System.STAY_ON_WHILE_PLUGGED_IN,   // moved to global
-        Settings.System.WIFI_USE_STATIC_IP,
-        Settings.System.WIFI_STATIC_IP,
-        Settings.System.WIFI_STATIC_GATEWAY,
-        Settings.System.WIFI_STATIC_NETMASK,
-        Settings.System.WIFI_STATIC_DNS1,
-        Settings.System.WIFI_STATIC_DNS2,
-        Settings.System.BLUETOOTH_DISCOVERABILITY,
-        Settings.System.BLUETOOTH_DISCOVERABILITY_TIMEOUT,
-        Settings.System.FONT_SCALE,
-        Settings.System.DIM_SCREEN,
-        Settings.System.SCREEN_OFF_TIMEOUT,
-        Settings.System.SCREEN_BRIGHTNESS_MODE,
-        Settings.System.ADAPTIVE_SLEEP,             // moved to secure
-        Settings.System.APPLY_RAMPING_RINGER,
-        Settings.System.VIBRATE_INPUT_DEVICES,
-        Settings.System.MODE_RINGER_STREAMS_AFFECTED,
-        Settings.System.TEXT_AUTO_REPLACE,
-        Settings.System.TEXT_AUTO_CAPS,
-        Settings.System.TEXT_AUTO_PUNCTUATE,
-        Settings.System.TEXT_SHOW_PASSWORD,
-        Settings.System.AUTO_TIME,                  // moved to global
-        Settings.System.AUTO_TIME_ZONE,             // moved to global
-        Settings.System.TIME_12_24,
-        Settings.System.DTMF_TONE_WHEN_DIALING,
-        Settings.System.DTMF_TONE_TYPE_WHEN_DIALING,
-        Settings.System.HEARING_AID,
-        Settings.System.TTY_MODE,
-        Settings.System.MASTER_MONO,
-        Settings.System.MASTER_BALANCE,
-        Settings.System.FOLD_LOCK_BEHAVIOR,
-        Settings.System.SOUND_EFFECTS_ENABLED,
-        Settings.System.HAPTIC_FEEDBACK_ENABLED,
-        Settings.System.POWER_SOUNDS_ENABLED,       // moved to global
-        Settings.System.DOCK_SOUNDS_ENABLED,        // moved to global
-        Settings.System.LOCKSCREEN_SOUNDS_ENABLED,
-        Settings.System.SHOW_WEB_SUGGESTIONS,
-        Settings.System.SIP_CALL_OPTIONS,
-        Settings.System.SIP_RECEIVE_CALLS,
-        Settings.System.POINTER_SPEED,
-        Settings.System.VIBRATE_ON,
-        Settings.System.VIBRATE_WHEN_RINGING,
-        Settings.System.RINGTONE,
-        Settings.System.LOCK_TO_APP_ENABLED,
-        Settings.System.NOTIFICATION_SOUND,
-        Settings.System.ACCELEROMETER_ROTATION,
-        Settings.System.SHOW_BATTERY_PERCENT,
-        Settings.System.ALARM_VIBRATION_INTENSITY,
-        Settings.System.MEDIA_VIBRATION_INTENSITY,
-        Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
-        Settings.System.RING_VIBRATION_INTENSITY,
-        Settings.System.HAPTIC_FEEDBACK_INTENSITY,
-        Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY,
-        Settings.System.KEYBOARD_VIBRATION_ENABLED,
-        Settings.System.HAPTIC_FEEDBACK_ENABLED,
-        Settings.System.DISPLAY_COLOR_MODE_VENDOR_HINT, // must precede DISPLAY_COLOR_MODE
-        Settings.System.DISPLAY_COLOR_MODE,
-        Settings.System.ALARM_ALERT,
-        Settings.System.NOTIFICATION_LIGHT_PULSE,
-        Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED,
-        Settings.System.CLOCKWORK_BLUETOOTH_SETTINGS_PREF,
-        Settings.System.UNREAD_NOTIFICATION_DOT_INDICATOR,
-        Settings.System.AUTO_LAUNCH_MEDIA_CONTROLS,
-        Settings.System.LOCALE_PREFERENCES,
-        Settings.System.TOUCHPAD_POINTER_SPEED,
-        Settings.System.TOUCHPAD_NATURAL_SCROLLING,
-        Settings.System.TOUCHPAD_TAP_TO_CLICK,
-        Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE,
-        Settings.System.CAMERA_FLASH_NOTIFICATION,
-        Settings.System.SCREEN_FLASH_NOTIFICATION,
-        Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
-        Settings.System.PEAK_REFRESH_RATE,
-        Settings.System.MIN_REFRESH_RATE,
-        Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
-        Settings.System.NOTIFICATION_COOLDOWN_ALL,
-        Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
-    };
+    public static final String[] SETTINGS_TO_BACKUP = getSettingsToBackUp();
+
+    private static String[] getSettingsToBackUp() {
+        List<String> settings = new ArrayList<>(List.of(
+                Settings.System.STAY_ON_WHILE_PLUGGED_IN,   // moved to global
+                Settings.System.WIFI_USE_STATIC_IP,
+                Settings.System.WIFI_STATIC_IP,
+                Settings.System.WIFI_STATIC_GATEWAY,
+                Settings.System.WIFI_STATIC_NETMASK,
+                Settings.System.WIFI_STATIC_DNS1,
+                Settings.System.WIFI_STATIC_DNS2,
+                Settings.System.BLUETOOTH_DISCOVERABILITY,
+                Settings.System.BLUETOOTH_DISCOVERABILITY_TIMEOUT,
+                Settings.System.FONT_SCALE,
+                Settings.System.DIM_SCREEN,
+                Settings.System.SCREEN_OFF_TIMEOUT,
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.ADAPTIVE_SLEEP,             // moved to secure
+                Settings.System.APPLY_RAMPING_RINGER,
+                Settings.System.VIBRATE_INPUT_DEVICES,
+                Settings.System.MODE_RINGER_STREAMS_AFFECTED,
+                Settings.System.TEXT_AUTO_REPLACE,
+                Settings.System.TEXT_AUTO_CAPS,
+                Settings.System.TEXT_AUTO_PUNCTUATE,
+                Settings.System.TEXT_SHOW_PASSWORD,
+                Settings.System.AUTO_TIME,                  // moved to global
+                Settings.System.AUTO_TIME_ZONE,             // moved to global
+                Settings.System.TIME_12_24,
+                Settings.System.DTMF_TONE_WHEN_DIALING,
+                Settings.System.DTMF_TONE_TYPE_WHEN_DIALING,
+                Settings.System.HEARING_AID,
+                Settings.System.TTY_MODE,
+                Settings.System.MASTER_MONO,
+                Settings.System.MASTER_BALANCE,
+                Settings.System.FOLD_LOCK_BEHAVIOR,
+                Settings.System.SOUND_EFFECTS_ENABLED,
+                Settings.System.HAPTIC_FEEDBACK_ENABLED,
+                Settings.System.POWER_SOUNDS_ENABLED,       // moved to global
+                Settings.System.DOCK_SOUNDS_ENABLED,        // moved to global
+                Settings.System.LOCKSCREEN_SOUNDS_ENABLED,
+                Settings.System.SHOW_WEB_SUGGESTIONS,
+                Settings.System.SIP_CALL_OPTIONS,
+                Settings.System.SIP_RECEIVE_CALLS,
+                Settings.System.POINTER_SPEED,
+                Settings.System.VIBRATE_ON,
+                Settings.System.VIBRATE_WHEN_RINGING,
+                Settings.System.RINGTONE,
+                Settings.System.LOCK_TO_APP_ENABLED,
+                Settings.System.NOTIFICATION_SOUND,
+                Settings.System.ACCELEROMETER_ROTATION,
+                Settings.System.SHOW_BATTERY_PERCENT,
+                Settings.System.ALARM_VIBRATION_INTENSITY,
+                Settings.System.MEDIA_VIBRATION_INTENSITY,
+                Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                Settings.System.RING_VIBRATION_INTENSITY,
+                Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+                Settings.System.HARDWARE_HAPTIC_FEEDBACK_INTENSITY,
+                Settings.System.KEYBOARD_VIBRATION_ENABLED,
+                Settings.System.HAPTIC_FEEDBACK_ENABLED,
+                Settings.System.DISPLAY_COLOR_MODE_VENDOR_HINT, // must precede DISPLAY_COLOR_MODE
+                Settings.System.DISPLAY_COLOR_MODE,
+                Settings.System.ALARM_ALERT,
+                Settings.System.NOTIFICATION_LIGHT_PULSE,
+                Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED,
+                Settings.System.CLOCKWORK_BLUETOOTH_SETTINGS_PREF,
+                Settings.System.UNREAD_NOTIFICATION_DOT_INDICATOR,
+                Settings.System.AUTO_LAUNCH_MEDIA_CONTROLS,
+                Settings.System.LOCALE_PREFERENCES,
+                Settings.System.TOUCHPAD_POINTER_SPEED,
+                Settings.System.TOUCHPAD_NATURAL_SCROLLING,
+                Settings.System.TOUCHPAD_TAP_TO_CLICK,
+                Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE,
+                Settings.System.CAMERA_FLASH_NOTIFICATION,
+                Settings.System.SCREEN_FLASH_NOTIFICATION,
+                Settings.System.SCREEN_FLASH_NOTIFICATION_COLOR,
+                Settings.System.NOTIFICATION_COOLDOWN_ENABLED,
+                Settings.System.NOTIFICATION_COOLDOWN_ALL,
+                Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED
+        ));
+        if (Flags.backUpSmoothDisplayAndForcePeakRefreshRate()) {
+            settings.add(Settings.System.PEAK_REFRESH_RATE);
+            settings.add(Settings.System.MIN_REFRESH_RATE);
+        }
+        return settings.toArray(new String[0]);
+    }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index f8bdcf6..5022395 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -210,6 +210,8 @@
         VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS_DURATION_MS, NONE_NEGATIVE_LONG_VALIDATOR);
         VALIDATORS.put(Global.STYLUS_EVER_USED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Global.MUTE_ALARM_STREAM_WITH_RINGER_MODE_USER_PREFERENCE, BOOLEAN_VALIDATOR);
 
         VALIDATORS.put(Global.Wearable.HAS_PAY_TOKENS, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.GMS_CHECKIN_TIMEOUT_MIN, ANY_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 6ad10cc..1481d97 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -33,6 +33,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.display.feature.flags.Flags;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -53,59 +55,6 @@
     public static final String HYBRID_SYSUI_BATTERY_WARNING_FLAGS =
             "hybrid_sysui_battery_warning_flags";
 
-    /**
-     * The following denylists contain settings that should *not* be backed up and restored to
-     * another device.  As a general rule, anything that is not user configurable should be
-     * denied (and conversely, things that *are* user configurable *should* be backed up)
-     */
-    private static final Set<String> BACKUP_DENY_LIST_SYSTEM_SETTINGS =
-            newHashSet(
-                    Settings.System.ADVANCED_SETTINGS, // candidate for backup?
-                    Settings.System.ALARM_ALERT_CACHE, // internal cache
-                    Settings.System.APPEND_FOR_LAST_AUDIBLE, // suffix deprecated since API 2
-                    Settings.System.EGG_MODE, // I am the lolrus
-                    Settings.System.END_BUTTON_BEHAVIOR, // bug?
-                    Settings.System
-                            .HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, // candidate for backup?
-                    Settings.System.LOCKSCREEN_DISABLED, // ?
-                    Settings.System.MEDIA_BUTTON_RECEIVER, // candidate for backup?
-                    Settings.System.MUTE_STREAMS_AFFECTED, //  candidate for backup?
-                    Settings.System.NOTIFICATION_SOUND_CACHE, // internal cache
-                    Settings.System.POINTER_LOCATION, // backup candidate?
-                    Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, // used for testing only
-                    Settings.System.RINGTONE_CACHE, // internal cache
-                    Settings.System.SCREEN_BRIGHTNESS, // removed in P
-                    Settings.System.SETUP_WIZARD_HAS_RUN, // Only used by SuW
-                    Settings.System.SHOW_GTALK_SERVICE_STATUS, // candidate for backup?
-                    Settings.System.SHOW_TOUCHES,
-                    Settings.System.SHOW_KEY_PRESSES,
-                    Settings.System.SHOW_ROTARY_INPUT,
-                    Settings.System.SIP_ADDRESS_ONLY, // value, not a setting
-                    Settings.System.SIP_ALWAYS, // value, not a setting
-                    Settings.System.SYSTEM_LOCALES, // bug?
-                    Settings.System.USER_ROTATION, // backup candidate?
-                    Settings.System.VIBRATE_IN_SILENT, // deprecated?
-                    Settings.System.VOLUME_ACCESSIBILITY, // used internally, changing value will
-                                                          // not change volume
-                    Settings.System.VOLUME_ALARM, // deprecated since API 2?
-                    Settings.System.VOLUME_ASSISTANT, // candidate for backup?
-                    Settings.System.VOLUME_BLUETOOTH_SCO, // deprecated since API 2?
-                    Settings.System.VOLUME_MASTER, // candidate for backup?
-                    Settings.System.VOLUME_MUSIC, // deprecated since API 2?
-                    Settings.System.VOLUME_NOTIFICATION, // deprecated since API 2?
-                    Settings.System.VOLUME_RING, // deprecated since API 2?
-                    Settings.System.VOLUME_SYSTEM, // deprecated since API 2?
-                    Settings.System.VOLUME_VOICE, // deprecated since API 2?
-                    Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
-                    Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
-                    Settings.System.SCREEN_BRIGHTNESS_FLOAT,
-                    Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
-                    Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE,
-                    Settings.System.WEAR_TTS_PREWARM_ENABLED,
-                    Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
-                    Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific
-                    );
-
     private static final Set<String> BACKUP_DENY_LIST_GLOBAL_SETTINGS =
             newHashSet(
                     Settings.Global.ACTIVITY_MANAGER_CONSTANTS,
@@ -737,6 +686,7 @@
                  Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS,
                  Settings.Secure.CONTENT_CAPTURE_ENABLED,
                  Settings.Secure.DEFAULT_INPUT_METHOD,
+                 Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD,
                  Settings.Secure.DEVICE_PAIRED,
                  Settings.Secure.DIALER_DEFAULT_APPLICATION,
                  Settings.Secure.DISABLED_PRINT_SERVICES,
@@ -862,7 +812,7 @@
         checkSettingsBackedUpOrDenied(
                 getCandidateSettings(Settings.System.class),
                 newHashSet(SystemSettings.SETTINGS_TO_BACKUP),
-                BACKUP_DENY_LIST_SYSTEM_SETTINGS);
+                getBackUpDenyListSystemSettings());
     }
 
     @Test
@@ -937,6 +887,69 @@
         checkSettingsBackedUpOrDenied(allSettings, keys, BACKUP_DENY_LIST_SECURE_SETTINGS);
     }
 
+    /**
+     * The following denylists contain settings that should *not* be backed up and restored to
+     * another device.  As a general rule, anything that is not user configurable should be
+     * denied (and conversely, things that *are* user configurable *should* be backed up)
+     */
+    private static Set<String> getBackUpDenyListSystemSettings() {
+        Set<String> settings =
+                newHashSet(
+                        Settings.System.ADVANCED_SETTINGS, // candidate for backup?
+                        Settings.System.ALARM_ALERT_CACHE, // internal cache
+                        Settings.System.APPEND_FOR_LAST_AUDIBLE, // suffix deprecated since API 2
+                        Settings.System.EGG_MODE, // I am the lolrus
+                        Settings.System.END_BUTTON_BEHAVIOR, // bug?
+                        Settings.System
+                                .HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY,
+                        // candidate for backup?
+                        Settings.System.LOCKSCREEN_DISABLED, // ?
+                        Settings.System.MEDIA_BUTTON_RECEIVER, // candidate for backup?
+                        Settings.System.MUTE_STREAMS_AFFECTED, //  candidate for backup?
+                        Settings.System.NOTIFICATION_SOUND_CACHE, // internal cache
+                        Settings.System.POINTER_LOCATION, // backup candidate?
+                        Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING,
+                        // used for testing only
+                        Settings.System.RINGTONE_CACHE, // internal cache
+                        Settings.System.SCREEN_BRIGHTNESS, // removed in P
+                        Settings.System.SETUP_WIZARD_HAS_RUN, // Only used by SuW
+                        Settings.System.SHOW_GTALK_SERVICE_STATUS, // candidate for backup?
+                        Settings.System.SHOW_TOUCHES,
+                        Settings.System.SHOW_KEY_PRESSES,
+                        Settings.System.SHOW_ROTARY_INPUT,
+                        Settings.System.SIP_ADDRESS_ONLY, // value, not a setting
+                        Settings.System.SIP_ALWAYS, // value, not a setting
+                        Settings.System.SYSTEM_LOCALES, // bug?
+                        Settings.System.USER_ROTATION, // backup candidate?
+                        Settings.System.VIBRATE_IN_SILENT, // deprecated?
+                        Settings.System.VOLUME_ACCESSIBILITY,
+                        // used internally, changing value will
+                        // not change volume
+                        Settings.System.VOLUME_ALARM, // deprecated since API 2?
+                        Settings.System.VOLUME_ASSISTANT, // candidate for backup?
+                        Settings.System.VOLUME_BLUETOOTH_SCO, // deprecated since API 2?
+                        Settings.System.VOLUME_MASTER, // candidate for backup?
+                        Settings.System.VOLUME_MUSIC, // deprecated since API 2?
+                        Settings.System.VOLUME_NOTIFICATION, // deprecated since API 2?
+                        Settings.System.VOLUME_RING, // deprecated since API 2?
+                        Settings.System.VOLUME_SYSTEM, // deprecated since API 2?
+                        Settings.System.VOLUME_VOICE, // deprecated since API 2?
+                        Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
+                        Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
+                        Settings.System.SCREEN_BRIGHTNESS_FLOAT,
+                        Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+                        Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE,
+                        Settings.System.WEAR_TTS_PREWARM_ENABLED,
+                        Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+                        Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific
+                );
+        if (!Flags.backUpSmoothDisplayAndForcePeakRefreshRate()) {
+            settings.add(Settings.System.MIN_REFRESH_RATE);
+            settings.add(Settings.System.PEAK_REFRESH_RATE);
+        }
+        return settings;
+    }
+
     private static void checkSettingsBackedUpOrDenied(
             Set<String> settings, Set<String> settingsToBackup, Set<String> denylist) {
         Set<String> settingsNotBackedUp = difference(settings, settingsToBackup);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index cc5dfc6..42107b7 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -65,6 +65,7 @@
 
                 "androidx.compose.runtime_runtime",
                 "androidx.compose.material3_material3",
+                "androidx.compose.material_material-icons-extended",
                 "androidx.activity_activity-compose",
                 "androidx.compose.animation_animation-graphics",
             ],
diff --git a/packages/SystemUI/aconfig/predictive_back.aconfig b/packages/SystemUI/aconfig/predictive_back.aconfig
new file mode 100644
index 0000000..1ad1666
--- /dev/null
+++ b/packages/SystemUI/aconfig/predictive_back.aconfig
@@ -0,0 +1,22 @@
+package: "com.android.systemui"
+
+flag {
+    name: "predictive_back_sysui"
+    namespace: "systemui"
+    description: "Predictive Back Dispatching for SysUI"
+    bug: "309545085"
+}
+
+flag {
+    name: "predictive_back_animate_shade"
+    namespace: "systemui"
+    description: "Enable Shade Animations"
+    bug: "309545085"
+}
+
+flag {
+    name: "predictive_back_animate_bouncer"
+    namespace: "systemui"
+    description: "Enable Predictive Back Animation in Bouncer"
+    bug: "309545085"
+}
\ No newline at end of file
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index aa0903c..5b21854 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -165,7 +165,7 @@
    name: "qs_new_tiles"
    namespace: "systemui"
    description: "Use the new tiles in the Quick Settings. Should have no behavior changes."
-   bug: "241772429"
+   bug: "311147395"
 }
 
 flag {
@@ -308,6 +308,13 @@
 }
 
 flag {
+   name: "run_fingerprint_detect_on_dismissible_keyguard"
+   namespace: "systemui"
+   description: "Run fingerprint detect instead of authenticate if the keyguard is dismissible."
+   bug: "311145851"
+}
+
+flag {
    name: "bluetooth_qs_tile_dialog_auto_on_toggle"
    namespace: "systemui"
    description: "Displays the auto on toggle in the bluetooth QS tile dialog"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
index 23fcb69..867bbb7d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt
@@ -65,12 +65,33 @@
             return dest;
         }
 
-        // Return range [-1, 1].
+        // Integer mod. GLSL es 1.0 doesn't have integer mod :(
+        int imod(int a, int b) {
+            return a - (b * (a / b));
+        }
+
+        ivec3 imod(ivec3 a, int b) {
+            return ivec3(imod(a.x, b), imod(a.y, b), imod(a.z, b));
+        }
+
+        // Integer based hash function with the return range of [-1, 1].
         vec3 hash(vec3 p) {
-            p = fract(p * vec3(.3456, .1234, .9876));
-            p += dot(p, p.yxz + 43.21);
-            p = (p.xxy + p.yxx) * p.zyx;
-            return (fract(sin(p) * 4567.1234567) - .5) * 2.;
+            ivec3 v = ivec3(p);
+            v = v * 1671731 + 10139267;
+
+            v.x += v.y * v.z;
+            v.y += v.z * v.x;
+            v.z += v.x * v.y;
+
+            ivec3 v2 = v / 65536; // v >> 16
+            v = imod((10 - imod((v + v2), 10)), 10); // v ^ v2
+
+            v.x += v.y * v.z;
+            v.y += v.z * v.x;
+            v.z += v.x * v.y;
+
+            // Use sin and cos to map the range to [-1, 1].
+            return vec3(sin(float(v.x)), cos(float(v.y)), sin(float(v.z)));
         }
 
         // Skew factors (non-uniform).
diff --git a/packages/SystemUI/compose/features/Android.bp b/packages/SystemUI/compose/features/Android.bp
index 796abf4b..5ab2235 100644
--- a/packages/SystemUI/compose/features/Android.bp
+++ b/packages/SystemUI/compose/features/Android.bp
@@ -38,6 +38,7 @@
         "androidx.compose.runtime_runtime",
         "androidx.compose.animation_animation-graphics",
         "androidx.compose.material3_material3",
+        "androidx.compose.material_material-icons-extended",
         "androidx.activity_activity-compose",
     ],
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 5a4e0a9..8bd5ddb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -205,8 +205,12 @@
                     list[index].size.dp().value,
                 )
             if (viewModel.isEditMode && dragDropState != null) {
-                DraggableItem(dragDropState = dragDropState, enabled = true, index = index) {
-                    isDragging ->
+                DraggableItem(
+                    dragDropState = dragDropState,
+                    enabled = true,
+                    index = index,
+                    size = size
+                ) { isDragging ->
                     val elevation by animateDpAsState(if (isDragging) 4.dp else 1.dp)
                     CommunalContent(
                         modifier = cardModifier,
@@ -326,7 +330,7 @@
     elevation: Dp = 0.dp,
 ) {
     when (model) {
-        is CommunalContentModel.Widget -> WidgetContent(model, size, elevation, modifier)
+        is CommunalContentModel.Widget -> WidgetContent(viewModel, model, size, elevation, modifier)
         is CommunalContentModel.WidgetPlaceholder -> WidgetPlaceholderContent(size)
         is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier)
         is CommunalContentModel.Tutorial -> TutorialContent(modifier)
@@ -347,6 +351,7 @@
 
 @Composable
 private fun WidgetContent(
+    viewModel: BaseCommunalViewModel,
     model: CommunalContentModel.Widget,
     size: SizeF,
     elevation: Dp,
@@ -359,9 +364,18 @@
         AndroidView(
             modifier = modifier,
             factory = { context ->
-                model.appWidgetHost
-                    .createView(context, model.appWidgetId, model.providerInfo)
-                    .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
+                // The AppWidgetHostView will inherit the interaction handler from the
+                // AppWidgetHost. So set the interaction handler here before creating the view, and
+                // then clear it after the view is created. This is a workaround due to the fact
+                // that the interaction handler cannot be specified when creating the view,
+                // and there are race conditions if it is set after the view is created.
+                model.appWidgetHost.setInteractionHandler(viewModel.getInteractionHandler())
+                val view =
+                    model.appWidgetHost
+                        .createView(context, model.appWidgetId, model.providerInfo)
+                        .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
+                model.appWidgetHost.setInteractionHandler(null)
+                view
             },
             // For reusing composition in lazy lists.
             onReset = {},
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 0d460aa8..1b40de4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal.ui.compose
 
+import android.util.SizeF
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
 import androidx.compose.foundation.gestures.scrollBy
@@ -233,6 +234,7 @@
     dragDropState: GridDragDropState,
     index: Int,
     enabled: Boolean,
+    size: SizeF,
     modifier: Modifier = Modifier,
     content: @Composable (isDragging: Boolean) -> Unit
 ) {
@@ -250,7 +252,11 @@
         } else {
             Modifier.animateItemPlacement()
         }
-    Box(modifier = modifier.then(draggingModifier), propagateMinConstraints = true) {
-        content(dragging)
+
+    Box(modifier) {
+        if (dragging) {
+            WidgetPlaceholderContent(size)
+        }
+        Box(modifier = draggingModifier, propagateMinConstraints = true) { content(dragging) }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 1fc2843..c8461d2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -45,10 +45,10 @@
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.classifier.FalsingA11yDelegate
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.log.SessionTracker
@@ -140,7 +140,7 @@
     @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
     @Mock private lateinit var audioManager: AudioManager
     @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
-    @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+    @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
     @Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate
     @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
     @Mock private lateinit var postureController: DevicePostureController
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index e5da1f8..36aa441 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -85,10 +85,10 @@
 import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
@@ -336,7 +336,7 @@
                 mSessionTracker,
                 mAlternateBouncerInteractor,
                 mInputManager,
-                mock(KeyguardFaceAuthInteractor.class),
+                mock(DeviceEntryFaceAuthInteractor.class),
                 mUdfpsKeyguardAccessibilityDelegate,
                 mSelectedUserInteractor,
                 mFpsUnlockTracker,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 1f8854f..335ac9d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -32,11 +32,11 @@
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -111,7 +111,7 @@
                 FakeTrustRepository(),
                 testScope.backgroundScope,
                 mSelectedUserInteractor,
-                mock(KeyguardFaceAuthInteractor::class.java),
+                mock(DeviceEntryFaceAuthInteractor::class.java),
             )
         mAlternateBouncerInteractor =
             AlternateBouncerInteractor(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 99c1874..93ba6a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.res.R
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
@@ -46,7 +46,7 @@
 @RunWith(AndroidJUnit4::class)
 class BouncerInteractorTest : SysuiTestCase() {
 
-    @Mock private lateinit var keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor
+    @Mock private lateinit var mDeviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor
 
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
@@ -67,7 +67,7 @@
         underTest =
             utils.bouncerInteractor(
                 authenticationInteractor = authenticationInteractor,
-                keyguardFaceAuthInteractor = keyguardFaceAuthInteractor,
+                deviceEntryFaceAuthInteractor = mDeviceEntryFaceAuthInteractor,
             )
     }
 
@@ -306,7 +306,7 @@
     fun intentionalUserInputEvent_notifiesFaceAuthInteractor() =
         testScope.runTest {
             underTest.onIntentionalUserInput()
-            verify(keyguardFaceAuthInteractor).onPrimaryBouncerUserInput()
+            verify(mDeviceEntryFaceAuthInteractor).onPrimaryBouncerUserInput()
         }
 
     companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
index bdf5041..c8560c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt
@@ -26,9 +26,9 @@
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.utils.os.FakeHandler
@@ -54,7 +54,7 @@
     @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
-    @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+    @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
     private val mainHandler = FakeHandler(Looper.getMainLooper())
     private lateinit var underTest: PrimaryBouncerInteractor
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index a3bf3f4..a0c2acc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -30,9 +30,9 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -63,7 +63,7 @@
     @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
     @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+    @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
 
     lateinit var bouncerInteractor: PrimaryBouncerInteractor
     private val mainHandler = FakeHandler(Looper.getMainLooper())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 314dfdf..f2f9705 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -33,14 +33,15 @@
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import javax.inject.Provider
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -56,7 +57,8 @@
     @Mock private lateinit var shadeViewController: ShadeViewController
     @Mock private lateinit var powerManager: PowerManager
 
-    private lateinit var testScope: TestScope
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var communalRepository: FakeCommunalRepository
@@ -71,8 +73,6 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        testScope = TestScope()
-
         val withDeps = CommunalInteractorFactory.create()
         keyguardRepository = withDeps.keyguardRepository
         communalRepository = withDeps.communalRepository
@@ -130,4 +130,17 @@
             assertThat(communalContent?.get(1))
                 .isInstanceOf(CommunalContentModel.Widget::class.java)
         }
+
+    @Test
+    fun interactionHandlerIgnoresClicks() {
+        val interactionHandler = underTest.getInteractionHandler()
+        assertThat(
+                interactionHandler.onInteraction(
+                    /* view = */ mock(),
+                    /* pendingIntent = */ mock(),
+                    /* response = */ mock()
+                )
+            )
+            .isEqualTo(false)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 8a71168..182cc5d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.communal.widgets.WidgetInteractionHandler
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.media.controls.ui.MediaHost
@@ -84,6 +85,7 @@
         underTest =
             CommunalViewModel(
                 withDeps.communalInteractor,
+                WidgetInteractionHandler(mock()),
                 withDeps.tutorialInteractor,
                 Provider { shadeViewController },
                 powerManager,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
similarity index 96%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 941d67f..6a14220 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.data.repository
+package com.android.systemui.deviceentry.data.repository
 
 import android.app.StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP
 import android.app.StatusBarManager.SESSION_KEYGUARD
@@ -37,10 +37,6 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.InstanceId.fakeInstanceId
 import com.android.internal.logging.UiEventLogger
-import com.android.keyguard.FaceAuthUiEvent
-import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
-import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED
-import com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
@@ -53,21 +49,32 @@
 import com.android.systemui.coroutines.FlowValue
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.display.data.repository.display
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags.KEYGUARD_WM_STATE_REFACTOR
+import com.android.systemui.keyguard.data.repository.BiometricType
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
-import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.log.FaceAuthenticationLogger
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index efd4f9b..530d127d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -265,7 +265,8 @@
                 powerInteractor = powerInteractor,
                 bouncerInteractor = bouncerInteractor,
                 simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
-                authenticationInteractor = dagger.Lazy { utils.authenticationInteractor() }
+                authenticationInteractor = dagger.Lazy { utils.authenticationInteractor() },
+                windowController = mock(),
             )
         startable.start()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 2e4986d..dd22976 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -46,18 +47,24 @@
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @EnableFlags(AconfigFlags.FLAG_SCENE_CONTAINER)
 class SceneContainerStartableTest : SysuiTestCase() {
 
+    @Mock private lateinit var windowController: NotificationShadeWindowController
+
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
     private val sceneInteractor = utils.sceneInteractor()
@@ -77,22 +84,30 @@
     private val falsingCollector: FalsingCollector = mock()
     private val powerInteractor = PowerInteractorFactory.create().powerInteractor
 
-    private val underTest =
-        SceneContainerStartable(
-            applicationScope = testScope.backgroundScope,
-            sceneInteractor = sceneInteractor,
-            deviceEntryInteractor = deviceEntryInteractor,
-            keyguardInteractor = keyguardInteractor,
-            flags = sceneContainerFlags,
-            sysUiState = sysUiState,
-            displayId = Display.DEFAULT_DISPLAY,
-            sceneLogger = mock(),
-            falsingCollector = falsingCollector,
-            powerInteractor = powerInteractor,
-            bouncerInteractor = bouncerInteractor,
-            simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
-            authenticationInteractor = dagger.Lazy { authenticationInteractor },
-        )
+    private lateinit var underTest: SceneContainerStartable
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            SceneContainerStartable(
+                applicationScope = testScope.backgroundScope,
+                sceneInteractor = sceneInteractor,
+                deviceEntryInteractor = deviceEntryInteractor,
+                keyguardInteractor = keyguardInteractor,
+                flags = sceneContainerFlags,
+                sysUiState = sysUiState,
+                displayId = Display.DEFAULT_DISPLAY,
+                sceneLogger = mock(),
+                falsingCollector = falsingCollector,
+                powerInteractor = powerInteractor,
+                bouncerInteractor = bouncerInteractor,
+                simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
+                authenticationInteractor = dagger.Lazy { authenticationInteractor },
+                windowController = windowController,
+            )
+    }
 
     @Test
     fun hydrateVisibility() =
@@ -655,6 +670,58 @@
             assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
         }
 
+    @Test
+    fun hydrateWindowFocus() =
+        testScope.runTest {
+            val currentDesiredSceneKey by
+                collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            val transitionStateFlow =
+                prepareState(
+                    isDeviceUnlocked = true,
+                    initialSceneKey = SceneKey.Gone,
+                )
+            assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone)
+            verify(windowController, never()).setNotificationShadeFocusable(anyBoolean())
+
+            underTest.start()
+            runCurrent()
+            verify(windowController, times(1)).setNotificationShadeFocusable(false)
+
+            sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason")
+            transitionStateFlow.value =
+                ObservableTransitionState.Transition(
+                    fromScene = SceneKey.Gone,
+                    toScene = SceneKey.Shade,
+                    progress = flowOf(0.5f),
+                    isInitiatedByUserInput = false,
+                    isUserInputOngoing = flowOf(false),
+                )
+            runCurrent()
+            verify(windowController, times(1)).setNotificationShadeFocusable(false)
+
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
+            transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade)
+            runCurrent()
+            verify(windowController, times(1)).setNotificationShadeFocusable(true)
+
+            sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
+            transitionStateFlow.value =
+                ObservableTransitionState.Transition(
+                    fromScene = SceneKey.Shade,
+                    toScene = SceneKey.Gone,
+                    progress = flowOf(0.5f),
+                    isInitiatedByUserInput = false,
+                    isUserInputOngoing = flowOf(false),
+                )
+            runCurrent()
+            verify(windowController, times(1)).setNotificationShadeFocusable(true)
+
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
+            transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
+            runCurrent()
+            verify(windowController, times(2)).setNotificationShadeFocusable(false)
+        }
+
     private fun TestScope.prepareState(
         isDeviceUnlocked: Boolean = false,
         isBypassEnabled: Boolean = false,
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml
index 1f671ac..f1017d8 100644
--- a/packages/SystemUI/res/values-sw600dp/config.xml
+++ b/packages/SystemUI/res/values-sw600dp/config.xml
@@ -40,6 +40,9 @@
     <!-- Whether to show bottom sheets edge to edge -->
     <bool name="config_edgeToEdgeBottomSheetDialog">false</bool>
 
+    <!-- Flag to activate drag notification to contents feature -->
+    <bool name="config_notificationToContents">true</bool>
+
     <!-- A collection of defaults for the quick affordances on the lock screen. Each item must be a
     string with two parts: the ID of the slot and the comma-delimited list of affordance IDs,
     separated by a colon ':' character. For example: <item>bottom_end:home,wallet</item>. The
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e7eb984..854bb0f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3262,6 +3262,9 @@
     <!-- Label for a button that, when clicked, sends the user to the app store to install an app. [CHAR LIMIT=64]. -->
     <string name="install_app">Install app</string>
 
+    <!-- Instructions informing the user they can swipe up on the lockscreen to dismiss [CHAR LIMIT=48]-->
+    <string name="dismissible_keyguard_swipe">Swipe to continue</string>
+
     <!--- Title of the dialog appearing when an external display is connected, asking whether to start mirroring [CHAR LIMIT=NONE]-->
     <string name="connected_display_dialog_start_mirroring">Mirror to external display?</string>
     <!--- Body of the mirroring dialog, shown when dual display is enabled. This signals that enabling mirroring will stop concurrent displays on a foldable device. [CHAR LIMIT=NONE]-->
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 2b41178..3a26ebf 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -66,6 +66,7 @@
         "kotlinx_coroutines",
         "dagger2",
         "jsr330",
+        "com_android_systemui_shared_flags_lib",
     ],
     resource_dirs: [
         "res",
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 131eb6b..c08b083 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -20,10 +20,11 @@
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
 
+import static com.android.systemui.shared.Flags.shadeAllowBackGesture;
+
 import android.annotation.IntDef;
 import android.content.Context;
 import android.content.res.Resources;
-import android.os.SystemProperties;
 import android.view.ViewConfiguration;
 import android.view.WindowManagerPolicyConstants;
 
@@ -132,8 +133,7 @@
             SYSUI_STATE_WAKEFULNESS_TRANSITION | SYSUI_STATE_AWAKE;
 
     // Whether the back gesture is allowed (or ignored) by the Shade
-    public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = SystemProperties.getBoolean(
-            "persist.wm.debug.shade_allow_back_gesture", false);
+    public static final boolean ALLOW_BACK_GESTURE_IN_SHADE = shadeAllowBackGesture();
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({SYSUI_STATE_SCREEN_PINNING,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index a8bf229..05eeac6f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -133,7 +133,8 @@
     static final int BOUNCER_DISMISS_EXTENDED_ACCESS = 3;
     // Bouncer is dismissed due to sim card unlock code entered.
     static final int BOUNCER_DISMISS_SIM = 4;
-
+    // Bouncer dismissed after being allowed to dismiss by forceDismissiblekeyguard
+    static final int BOUNCER_DISMISSIBLE_KEYGUARD = 5;
     private static final String TAG = "KeyguardSecurityView";
 
     // Make the view move slower than the finger, as if the spring were applying force.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 5e35e77..e457ca1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -21,6 +21,7 @@
 
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
+import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISSIBLE_KEYGUARD;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_PASSWORD;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_SIM;
@@ -78,10 +79,10 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
@@ -135,7 +136,7 @@
     private final SessionTracker mSessionTracker;
     private final Optional<SideFpsController> mSideFpsController;
     private final FalsingA11yDelegate mFalsingA11yDelegate;
-    private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+    private final DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
     private final BouncerMessageInteractor mBouncerMessageInteractor;
     private int mTranslationY;
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -216,7 +217,7 @@
         @Override
         public void onUserInput() {
             mBouncerMessageInteractor.onPrimaryBouncerUserInput();
-            mKeyguardFaceAuthInteractor.onPrimaryBouncerUserInput();
+            mDeviceEntryFaceAuthInteractor.onPrimaryBouncerUserInput();
         }
 
         @Override
@@ -347,11 +348,11 @@
     private final SwipeListener mSwipeListener = new SwipeListener() {
         @Override
         public void onSwipeUp() {
-            if (mKeyguardFaceAuthInteractor.canFaceAuthRun()) {
+            if (mDeviceEntryFaceAuthInteractor.canFaceAuthRun()) {
                 mKeyguardSecurityCallback.userActivity();
             }
-            mKeyguardFaceAuthInteractor.onSwipeUpOnBouncer();
-            if (mKeyguardFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()) {
+            mDeviceEntryFaceAuthInteractor.onSwipeUpOnBouncer();
+            if (mDeviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()) {
                 mUpdateMonitor.requestActiveUnlock(
                         ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
                         "swipeUpOnBouncer");
@@ -456,7 +457,7 @@
             TelephonyManager telephonyManager,
             ViewMediatorCallback viewMediatorCallback,
             AudioManager audioManager,
-            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
+            DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
             BouncerMessageInteractor bouncerMessageInteractor,
             Provider<JavaAdapter> javaAdapter,
             SelectedUserInteractor selectedUserInteractor,
@@ -495,7 +496,7 @@
         mTelephonyManager = telephonyManager;
         mViewMediatorCallback = viewMediatorCallback;
         mAudioManager = audioManager;
-        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+        mDeviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor;
         mBouncerMessageInteractor = bouncerMessageInteractor;
         mSelectedUserInteractor = selectedUserInteractor;
         mDeviceEntryInteractor = deviceEntryInteractor;
@@ -862,7 +863,11 @@
         boolean finish = false;
         int eventSubtype = -1;
         BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN;
-        if (mUpdateMonitor.getUserHasTrust(targetUserId)) {
+        if (mUpdateMonitor.forceIsDismissibleIsKeepingDeviceUnlocked()) {
+            finish = true;
+            eventSubtype = BOUNCER_DISMISSIBLE_KEYGUARD;
+            // TODO: b/308417021 add UI event
+        } else if (mUpdateMonitor.getUserHasTrust(targetUserId)) {
             finish = true;
             eventSubtype = BOUNCER_DISMISS_EXTENDED_ACCESS;
             uiEvent = BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 4d84d0b..fe96099 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -102,6 +102,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.foldables.FoldGracePeriodProvider;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.UiEventLogger;
@@ -113,24 +114,26 @@
 import com.android.settingslib.fuelgauge.BatteryStatus;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
+import com.android.systemui.deviceentry.domain.interactor.FaceAuthenticationListener;
+import com.android.systemui.deviceentry.shared.model.AcquiredFaceAuthenticationStatus;
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus;
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus;
+import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus;
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus;
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus;
+import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.DumpsysTableLogger;
-import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.shared.constants.TrustAgentUiEvent;
-import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.FaceDetectionStatus;
-import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.clocks.WeatherData;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -306,6 +309,10 @@
     private boolean mKeyguardOccluded;
     private boolean mCredentialAttempted;
     private boolean mKeyguardGoingAway;
+    /**
+     * Whether the keyguard is forced into a dismissible state.
+     */
+    private boolean mForceIsDismissible;
     private boolean mGoingToSleep;
     private boolean mPrimaryBouncerFullyShown;
     private boolean mPrimaryBouncerIsOrWillBeShowing;
@@ -359,7 +366,10 @@
     @Nullable
     private final BiometricManager mBiometricManager;
     @Nullable
-    private KeyguardFaceAuthInteractor mFaceAuthInteractor;
+    private DeviceEntryFaceAuthInteractor mFaceAuthInteractor;
+    @VisibleForTesting
+    protected FoldGracePeriodProvider mFoldGracePeriodProvider =
+            new FoldGracePeriodProvider();
     private final DevicePostureController mDevicePostureController;
     private final TaskStackChangeListeners mTaskStackChangeListeners;
     private final IActivityTaskManager mActivityTaskManager;
@@ -373,6 +383,7 @@
     private List<SubscriptionInfo> mSubscriptionInfo;
     @VisibleForTesting
     protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+    private boolean mFingerprintDetectRunning;
     private boolean mIsDreaming;
     private boolean mLogoutEnabled;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -725,6 +736,14 @@
     }
 
     /**
+     * Updates KeyguardUpdateMonitor's internal state to know the device should remain unlocked
+     * until the next signal to lock. Does nothing if the keyguard is already showing.
+     */
+    public void tryForceIsDismissibleKeyguard() {
+        setForceIsDismissibleKeyguard(true);
+    }
+
+    /**
      * Updates KeyguardUpdateMonitor's internal state to know if keyguard is going away.
      */
     public void setKeyguardGoingAway(boolean goingAway) {
@@ -986,6 +1005,7 @@
         final boolean wasCancellingRestarting = mFingerprintRunningState
                 == BIOMETRIC_STATE_CANCELLING_RESTARTING;
         mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+        mFingerprintDetectRunning = false;
         if (wasCancellingRestarting) {
             KeyguardUpdateMonitor.this.updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         } else {
@@ -1094,6 +1114,9 @@
         boolean wasRunning = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
         boolean isRunning = fingerprintRunningState == BIOMETRIC_STATE_RUNNING;
         mFingerprintRunningState = fingerprintRunningState;
+        if (mFingerprintRunningState == BIOMETRIC_STATE_STOPPED) {
+            mFingerprintDetectRunning = false;
+        }
         mLogger.logFingerprintRunningState(mFingerprintRunningState);
         // Clients of KeyguardUpdateMonitor don't care about the internal state about the
         // asynchronousness of the cancel cycle. So only notify them if the actually running state
@@ -1266,14 +1289,14 @@
         return getFaceAuthInteractor() != null && getFaceAuthInteractor().isRunning();
     }
 
-    private @Nullable KeyguardFaceAuthInteractor getFaceAuthInteractor() {
+    private @Nullable DeviceEntryFaceAuthInteractor getFaceAuthInteractor() {
         return mFaceAuthInteractor;
     }
 
     /**
      * Set the face auth interactor that should be used for initiating face authentication.
      */
-    public void setFaceAuthInteractor(KeyguardFaceAuthInteractor faceAuthInteractor) {
+    public void setFaceAuthInteractor(DeviceEntryFaceAuthInteractor faceAuthInteractor) {
         if (mFaceAuthInteractor != null) {
             mFaceAuthInteractor.unregisterListener(mFaceAuthenticationListener);
         }
@@ -1356,7 +1379,7 @@
      * @return whether the current user has been authenticated with face. This may be true
      * on the lockscreen if the user doesn't have bypass enabled.
      *
-     * @deprecated Use {@link KeyguardFaceAuthInteractor#isAuthenticated()}
+     * @deprecated Use {@link DeviceEntryFaceAuthInteractor#isAuthenticated()}
      */
     @Deprecated
     public boolean getIsFaceAuthenticated() {
@@ -1364,7 +1387,18 @@
     }
 
     public boolean getUserCanSkipBouncer(int userId) {
-        return getUserHasTrust(userId) || getUserUnlockedWithBiometric(userId);
+        return getUserHasTrust(userId) || getUserUnlockedWithBiometric(userId)
+                || forceIsDismissibleIsKeepingDeviceUnlocked();
+    }
+
+    /**
+     * Whether the keyguard should be kept unlocked for the folding grace period.
+     */
+    public boolean forceIsDismissibleIsKeepingDeviceUnlocked() {
+        if (mFoldGracePeriodProvider.isEnabled()) {
+            return mForceIsDismissible && isUnlockingWithForceKeyguardDismissibleAllowed();
+        }
+        return false;
     }
 
     public boolean getUserHasTrust(int userId) {
@@ -1387,7 +1421,7 @@
 
     /**
      * Returns whether the user is unlocked with face.
-     * @deprecated Use {@link KeyguardFaceAuthInteractor#isAuthenticated()} instead
+     * @deprecated Use {@link DeviceEntryFaceAuthInteractor#isAuthenticated()} instead
      */
     @Deprecated
     public boolean isCurrentUserUnlockedWithFace() {
@@ -1468,6 +1502,10 @@
         return isUnlockingWithBiometricAllowed(true);
     }
 
+    private boolean isUnlockingWithForceKeyguardDismissibleAllowed() {
+        return isUnlockingWithBiometricAllowed(false);
+    }
+
     public boolean isUnlockingWithBiometricAllowed(boolean isStrongBiometric) {
         // StrongAuthTracker#isUnlockingWithBiometricAllowed includes
         // STRONG_AUTH_REQUIRED_AFTER_LOCKOUT which is the same as mFingerprintLockedOutPermanent;
@@ -1979,6 +2017,7 @@
 
     protected void handleStartedGoingToSleep(int arg1) {
         Assert.isMainThread();
+        setForceIsDismissibleKeyguard(false);
         clearFingerprintRecognized();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -2066,6 +2105,7 @@
     @VisibleForTesting
     void resetBiometricListeningState() {
         mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
+        mFingerprintDetectRunning = false;
     }
 
     @VisibleForTesting
@@ -2457,7 +2497,7 @@
 
     /**
      * @return true if there's at least one face enrolled
-     * @deprecated Use {@link KeyguardFaceAuthInteractor#isFaceAuthEnabledAndEnrolled()}
+     * @deprecated Use {@link DeviceEntryFaceAuthInteractor#isFaceAuthEnabledAndEnrolled()}
      */
     @Deprecated
     public boolean isFaceEnabledAndEnrolled() {
@@ -2504,8 +2544,10 @@
             return;
         }
         final boolean shouldListenForFingerprint = shouldListenForFingerprint(isUdfpsSupported());
-        final boolean runningOrRestarting = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING
+        final boolean running = mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
+        final boolean runningOrRestarting = running
                 || mFingerprintRunningState == BIOMETRIC_STATE_CANCELLING_RESTARTING;
+        final boolean runDetect = shouldRunFingerprintDetect();
         if (runningOrRestarting && !shouldListenForFingerprint) {
             if (action == BIOMETRIC_ACTION_START) {
                 mLogger.v("Ignoring stopListeningForFingerprint()");
@@ -2517,10 +2559,24 @@
                 mLogger.v("Ignoring startListeningForFingerprint()");
                 return;
             }
-            startListeningForFingerprint();
+            startListeningForFingerprint(runDetect);
+        } else if (running && runDetect && !mFingerprintDetectRunning) {
+            if (action == BIOMETRIC_ACTION_STOP) {
+                mLogger.v("Ignoring startListeningForFingerprint(detect)");
+                return;
+            }
+            // stop running authentication and start running fingerprint detection
+            stopListeningForFingerprint();
+            startListeningForFingerprint(true);
         }
     }
 
+    private boolean shouldRunFingerprintDetect() {
+        return !isUnlockingWithFingerprintAllowed()
+                || (Flags.runFingerprintDetectOnDismissibleKeyguard()
+                && getUserCanSkipBouncer(mSelectedUserInteractor.getSelectedUserId()));
+    }
+
     /**
      * If a user is encrypted or not.
      * This is NOT related to the lock screen being visible or not.
@@ -2776,7 +2832,6 @@
                         && biometricEnabledForUser
                         && !isUserInLockdown(user);
         final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
-        final boolean isSideFps = isSfpsSupported() && isSfpsEnrolled();
         final boolean shouldListenBouncerState =
                 !strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing;
 
@@ -2822,7 +2877,7 @@
     /**
      * If face auth is allows to scan on this exact moment.
      *
-     * @deprecated Use {@link KeyguardFaceAuthInteractor#canFaceAuthRun()}
+     * @deprecated Use {@link DeviceEntryFaceAuthInteractor#canFaceAuthRun()}
      */
     @Deprecated
     public boolean shouldListenForFace() {
@@ -2839,7 +2894,7 @@
         }
     }
 
-    private void startListeningForFingerprint() {
+    private void startListeningForFingerprint(boolean runDetect) {
         final int userId = mSelectedUserInteractor.getSelectedUserId();
         final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
         if (mFingerprintCancelSignal != null) {
@@ -2869,18 +2924,20 @@
                         mFingerprintInteractiveToAuthProvider.getVendorExtension(userId));
             }
 
-            if (!isUnlockingWithFingerprintAllowed()) {
+            if (runDetect) {
                 mLogger.v("startListeningForFingerprint - detect");
                 mFpm.detectFingerprint(
                         mFingerprintCancelSignal,
                         mFingerprintDetectionCallback,
                         fingerprintAuthenticateOptions);
+                mFingerprintDetectRunning = true;
             } else {
                 mLogger.v("startListeningForFingerprint");
                 mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal,
                         mFingerprintAuthenticationCallback,
                         null /* handler */,
                         fingerprintAuthenticateOptions);
+                mFingerprintDetectRunning = false;
             }
             setFingerprintRunningState(BIOMETRIC_STATE_RUNNING);
         }
@@ -2891,7 +2948,7 @@
     }
 
     /**
-     * @deprecated Use {@link KeyguardFaceAuthInteractor#isLockedOut()}
+     * @deprecated Use {@link DeviceEntryFaceAuthInteractor#isLockedOut()}
      */
     @Deprecated
     public boolean isFaceLockedOut() {
@@ -2932,7 +2989,7 @@
     }
 
     /**
-     * @deprecated Use {@link KeyguardFaceAuthInteractor#isFaceAuthEnabledAndEnrolled()}
+     * @deprecated Use {@link DeviceEntryFaceAuthInteractor#isFaceAuthEnabledAndEnrolled()}
      */
     @VisibleForTesting
     @Deprecated
@@ -3033,6 +3090,7 @@
     void handleUserSwitching(int userId, Runnable resultCallback) {
         mLogger.logUserSwitching(userId, "from UserTracker");
         Assert.isMainThread();
+        setForceIsDismissibleKeyguard(false);
         clearFingerprintRecognized();
         boolean trustUsuallyManaged = mTrustManager.isTrustUsuallyManaged(userId);
         mLogger.logTrustUsuallyManagedUpdated(userId, mUserTrustIsUsuallyManaged.get(userId),
@@ -3611,6 +3669,31 @@
         }
     }
 
+    private void setForceIsDismissibleKeyguard(boolean forceIsDismissible) {
+        Assert.isMainThread();
+        if (!mFoldGracePeriodProvider.isEnabled()) {
+            // never send updates if the feature isn't enabled
+            return;
+        }
+        if (mKeyguardShowing && forceIsDismissible) {
+            // never keep the device unlocked if the keyguard was already showing
+            mLogger.d("Skip setting forceIsDismissibleKeyguard to true. "
+                    + "Keyguard already showing.");
+            return;
+        }
+        if (mForceIsDismissible != forceIsDismissible) {
+            mForceIsDismissible = forceIsDismissible;
+            mLogger.logForceIsDismissibleKeyguard(mForceIsDismissible);
+            for (int i = 0; i < mCallbacks.size(); i++) {
+                KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+                if (cb != null) {
+                    cb.onForceIsDismissibleChanged(forceIsDismissibleIsKeepingDeviceUnlocked());
+                }
+            }
+        }
+
+    }
+
     public boolean isSimPinVoiceSecure() {
         // TODO: only count SIMs that handle voice
         return isSimPinSecure();
@@ -3872,10 +3955,14 @@
     @Override
     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
         pw.println("KeyguardUpdateMonitor state:");
+        pw.println("  forceIsDismissible=" + mForceIsDismissible);
+        pw.println("  forceIsDismissibleIsKeepingDeviceUnlocked="
+                + forceIsDismissibleIsKeepingDeviceUnlocked());
         pw.println("  getUserHasTrust()=" + getUserHasTrust(
                 mSelectedUserInteractor.getSelectedUserId()));
         pw.println("  getUserUnlockedWithBiometric()="
                 + getUserUnlockedWithBiometric(mSelectedUserInteractor.getSelectedUserId()));
+        pw.println("  mFingerprintDetectRunning=" + mFingerprintDetectRunning);
         pw.println("  SIM States:");
         for (SimData data : mSimDatas.values()) {
             pw.println("    " + data.toString());
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 9d216dce..7ac5ac2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -339,4 +339,9 @@
      * On biometric enrollment state changed
      */
     public void onBiometricEnrollmentStateChanged(BiometricSourceType biometricSourceType) { }
+
+    /**
+     * On force is dismissible state changed.
+     */
+    public void onForceIsDismissibleChanged(boolean forceIsDismissible) { }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 055ca56..1f4e732 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -639,4 +639,12 @@
             { "fingerprint acquire message: $int1" }
         )
     }
+    fun logForceIsDismissibleKeyguard(keepUnlocked: Boolean) {
+        logBuffer.log(
+                TAG,
+                DEBUG,
+                { bool1 = keepUnlocked },
+                { "keepUnlockedOnFold changed to: $bool1" }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
index 066cba23..6076f32 100644
--- a/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/back/domain/interactor/BackActionInteractor.kt
@@ -22,10 +22,9 @@
 import android.window.OnBackInvokedDispatcher
 import android.window.WindowOnBackInvokedDispatcher
 import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.predictiveBackAnimateShade
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
 import com.android.systemui.shade.QuickSettingsController
@@ -48,14 +47,13 @@
     private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
     private val shadeController: ShadeController,
     private val notificationShadeWindowController: NotificationShadeWindowController,
-    private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor,
-    featureFlags: FeatureFlags,
+    private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor
 ) : CoreStartable {
 
     private var isCallbackRegistered = false
 
     private val callback =
-        if (featureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE)) {
+        if (predictiveBackAnimateShade()) {
             /**
              * New callback that handles back gesture invoked, cancel, progress and provides
              * feedback via Shade animation.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 6345312..45967c6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -32,13 +32,13 @@
 import com.android.settingslib.Utils
 import com.android.systemui.CoreStartable
 import com.android.systemui.Flags.lightRevealMigration
-import com.android.systemui.res.R
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.CircleReveal
 import com.android.systemui.statusbar.LiftReveal
 import com.android.systemui.statusbar.LightRevealEffect
@@ -50,6 +50,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.ViewController
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import java.io.PrintWriter
 import javax.inject.Inject
 import javax.inject.Provider
@@ -62,6 +63,7 @@
  *
  * The ripple uses the accent color of the current theme.
  */
+@ExperimentalCoroutinesApi
 @SysUISingleton
 class AuthRippleController @Inject constructor(
     private val sysuiContext: Context,
@@ -75,7 +77,6 @@
     private val udfpsControllerProvider: Provider<UdfpsController>,
     private val statusBarStateController: StatusBarStateController,
     private val displayMetrics: DisplayMetrics,
-    private val featureFlags: FeatureFlags,
     private val logger: KeyguardLogger,
     private val biometricUnlockController: BiometricUnlockController,
     private val lightRevealScrim: LightRevealScrim,
@@ -313,6 +314,18 @@
                 mView.fadeDwellRipple()
             }
         }
+
+        override fun onBiometricDetected(
+                userId: Int,
+                biometricSourceType: BiometricSourceType,
+                isStrongBiometric: Boolean
+        ) {
+            // TODO (b/309804148): add support detect auth ripple for deviceEntryUdfpsRefactor
+            if (!DeviceEntryUdfpsRefactor.isEnabled &&
+                    keyguardUpdateMonitor.getUserCanSkipBouncer(userId)) {
+                showUnlockRipple(biometricSourceType)
+            }
+        }
     }
 
     private val configurationChangedListener =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
index cb75049..3ea1632 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
@@ -22,7 +22,7 @@
 import android.view.accessibility.AccessibilityNodeInfo
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.res.R
 import javax.inject.Inject
 
@@ -35,7 +35,7 @@
 @Inject
 constructor(
     @Main private val resources: Resources,
-    private val faceAuthInteractor: KeyguardFaceAuthInteractor,
+    private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
 ) : View.AccessibilityDelegate() {
     override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
         super.onInitializeAccessibilityNodeInfo(host, info)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 2fd13b3..d664637 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -80,11 +80,11 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor;
 import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.ScreenLifecycle;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
@@ -149,7 +149,7 @@
     @NonNull private final DumpManager mDumpManager;
     @NonNull private final SystemUIDialogManager mDialogManager;
     @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+    @NonNull private final DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
     @NonNull private final VibratorHelper mVibrator;
     @NonNull private final FalsingManager mFalsingManager;
     @NonNull private final PowerManager mPowerManager;
@@ -664,7 +664,7 @@
             @NonNull SessionTracker sessionTracker,
             @NonNull AlternateBouncerInteractor alternateBouncerInteractor,
             @NonNull InputManager inputManager,
-            @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
+            @NonNull DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
             @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
             @NonNull SelectedUserInteractor selectedUserInteractor,
             @NonNull FpsUnlockTracker fpsUnlockTracker,
@@ -736,7 +736,7 @@
                     }
                     return Unit.INSTANCE;
                 });
-        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+        mDeviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor;
 
         final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController();
         mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController);
@@ -1027,7 +1027,7 @@
         if (!mOnFingerDown) {
             playStartHaptic();
 
-            mKeyguardFaceAuthInteractor.onUdfpsSensorTouched();
+            mDeviceEntryFaceAuthInteractor.onUdfpsSensorTouched();
         }
         mOnFingerDown = true;
         mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, pointerId, x, y,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 81d822f..c8ce245 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -29,7 +29,7 @@
 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
 import javax.inject.Inject
@@ -52,7 +52,7 @@
     @Application private val applicationContext: Context,
     private val repository: BouncerRepository,
     private val authenticationInteractor: AuthenticationInteractor,
-    private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
+    private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
     private val falsingInteractor: FalsingInteractor,
     private val powerInteractor: PowerInteractor,
     private val simBouncerInteractor: SimBouncerInteractor,
@@ -100,7 +100,7 @@
      * user's pocket or by the user's face while holding their device up to their ear.
      */
     fun onIntentionalUserInput() {
-        keyguardFaceAuthInteractor.onPrimaryBouncerUserInput()
+        deviceEntryFaceAuthInteractor.onPrimaryBouncerUserInput()
         powerInteractor.onUserTouch()
         falsingInteractor.updateFalseConfidence(FalsingClassifier.Result.passed(0.6))
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
index b587ed8..ef4554c 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractor.kt
@@ -30,9 +30,9 @@
 import com.android.systemui.bouncer.shared.model.Message
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.flags.SystemPropertiesHelper
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.res.R.string.bouncer_face_not_recognized
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 621ca5d..654fa22 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -38,9 +38,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
 import com.android.systemui.shared.system.SysUiStatsLog
@@ -77,7 +77,7 @@
     private val trustRepository: TrustRepository,
     @Application private val applicationScope: CoroutineScope,
     private val selectedUserInteractor: SelectedUserInteractor,
-    private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
+    private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
 ) {
     private val passiveAuthBouncerDelay =
         context.resources.getInteger(R.integer.primary_bouncer_passive_auth_delay).toLong()
@@ -429,7 +429,7 @@
                 keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()
 
         return !needsFullscreenBouncer() &&
-            (keyguardFaceAuthInteractor.canFaceAuthRun() || canRunActiveUnlock)
+            (deviceEntryFaceAuthInteractor.canFaceAuthRun() || canRunActiveUnlock)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 577e404..c34a8df 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -20,6 +20,7 @@
 import android.os.PowerManager
 import android.os.SystemClock
 import android.view.MotionEvent
+import android.widget.RemoteViews
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalSceneKey
@@ -101,4 +102,7 @@
 
     /** Called as the UI requests opening the widget editor. */
     open fun onOpenWidgetEditor() {}
+
+    /** Gets the interaction handler used to handle taps on a remote view */
+    abstract fun getInteractionHandler(): RemoteViews.InteractionHandler
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 368df9e..da7bd34 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.ui.viewmodel
 
 import android.os.PowerManager
+import android.widget.RemoteViews
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.dagger.SysUISingleton
@@ -49,4 +50,9 @@
 
     override fun onReorderWidgets(widgetIdToPriorityMap: Map<Int, Int>) =
         communalInteractor.updateWidgetOrder(widgetIdToPriorityMap)
+
+    override fun getInteractionHandler(): RemoteViews.InteractionHandler {
+        // Ignore all interactions in edit mode.
+        return RemoteViews.InteractionHandler { _, _, _ -> false }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index abf1986..2fae8b5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -17,9 +17,11 @@
 package com.android.systemui.communal.ui.viewmodel
 
 import android.os.PowerManager
+import android.widget.RemoteViews
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor
 import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.widgets.WidgetInteractionHandler
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.media.controls.ui.MediaHost
 import com.android.systemui.media.dagger.MediaModule
@@ -39,6 +41,7 @@
 @Inject
 constructor(
     private val communalInteractor: CommunalInteractor,
+    private val interactionHandler: WidgetInteractionHandler,
     tutorialInteractor: CommunalTutorialInteractor,
     shadeViewController: Provider<ShadeViewController>,
     powerManager: PowerManager,
@@ -60,4 +63,6 @@
         }
 
     override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor()
+
+    override fun getInteractionHandler(): RemoteViews.InteractionHandler = interactionHandler
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index c936c63..0f94a92 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -23,6 +23,7 @@
 import android.os.RemoteException
 import android.util.Log
 import android.view.IWindowManager
+import android.view.WindowInsets
 import androidx.activity.ComponentActivity
 import androidx.activity.result.ActivityResultLauncher
 import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
@@ -79,6 +80,10 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
+        val windowInsetsController = window.decorView.windowInsetsController
+        windowInsetsController?.hide(WindowInsets.Type.systemBars())
+        window.setDecorFitsSystemWindows(false)
+
         setCommunalEditWidgetActivityContent(
             activity = this,
             viewModel = communalViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
new file mode 100644
index 0000000..c8db70b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.app.PendingIntent
+import android.view.View
+import android.widget.RemoteViews
+import com.android.systemui.plugins.ActivityStarter
+import javax.inject.Inject
+
+class WidgetInteractionHandler
+@Inject
+constructor(
+    private val activityStarter: ActivityStarter,
+) : RemoteViews.InteractionHandler {
+    override fun onInteraction(
+        view: View,
+        pendingIntent: PendingIntent,
+        response: RemoteViews.RemoteResponse
+    ): Boolean =
+        when {
+            pendingIntent.isActivity -> startActivity(pendingIntent)
+            else ->
+                RemoteViews.startPendingIntent(view, pendingIntent, response.getLaunchOptions(view))
+        }
+
+    private fun startActivity(pendingIntent: PendingIntent): Boolean {
+        activityStarter.startPendingIntentMaybeDismissingKeyguard(
+            /* intent = */ pendingIntent,
+            /* intentSentUiThreadCallback = */ null,
+            // TODO(b/318758390): Properly animate activities started from widgets.
+            /* animationController = */ null
+        )
+        return true
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index e71007b..8831dc6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -484,7 +484,9 @@
                     }
                 },
                 PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
-                null,
+                ActivityOptions.makeBasic()
+                    .setPendingIntentCreatorBackgroundActivityStartMode(
+                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle(),
                 userTracker.userHandle
         )
 
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
index b915418..71b5ab2 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
@@ -1,6 +1,7 @@
 package com.android.systemui.deviceentry
 
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
+import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import dagger.Module
 import dagger.multibindings.Multibinds
@@ -9,6 +10,7 @@
     includes =
         [
             DeviceEntryRepositoryModule::class,
+            FaceWakeUpTriggersConfigModule::class,
         ],
 )
 abstract class DeviceEntryModule {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
rename to packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
index 17d7836..7a70c4a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.data.repository
+package com.android.systemui.deviceentry.data.repository
 
 import android.app.StatusBarManager
 import android.content.Context
@@ -22,7 +22,6 @@
 import android.os.CancellationSignal
 import com.android.internal.logging.InstanceId
 import com.android.internal.logging.UiEventLogger
-import com.android.keyguard.FaceAuthUiEvent
 import com.android.systemui.Dumpable
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
@@ -32,19 +31,27 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
+import com.android.systemui.deviceentry.shared.model.AcquiredFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.BiometricType
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FaceAuthTableLog
+import com.android.systemui.keyguard.data.repository.FaceDetectTableLog
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.AcquiredFaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
-import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.log.FaceAuthenticationLogger
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfig.kt
similarity index 83%
rename from packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt
rename to packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfig.kt
index 84a6b09..9d6718b 100644
--- a/packages/SystemUI/src/com/android/keyguard/FaceWakeUpTriggersConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfig.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,28 +14,35 @@
  * limitations under the License.
  */
 
-package com.android.keyguard
+package com.android.systemui.deviceentry.data.repository
 
 import android.content.res.Resources
 import android.os.Build
 import android.os.PowerManager
 import com.android.systemui.Dumpable
-import com.android.systemui.res.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.res.R
 import com.android.systemui.util.settings.GlobalSettings
+import dagger.Binds
+import dagger.Module
 import java.io.PrintWriter
 import java.util.stream.Collectors
 import javax.inject.Inject
 
 /** Determines which device wake-ups should trigger passive authentication. */
+interface FaceWakeUpTriggersConfig {
+    fun shouldTriggerFaceAuthOnWakeUpFrom(@PowerManager.WakeReason pmWakeReason: Int): Boolean
+    fun shouldTriggerFaceAuthOnWakeUpFrom(wakeReason: WakeSleepReason): Boolean
+}
+
 @SysUISingleton
-class FaceWakeUpTriggersConfig
+class FaceWakeUpTriggersConfigImpl
 @Inject
 constructor(@Main resources: Resources, globalSettings: GlobalSettings, dumpManager: DumpManager) :
-    Dumpable {
+    Dumpable, FaceWakeUpTriggersConfig {
     private val defaultTriggerFaceAuthOnWakeUpFrom: Set<Int> =
         resources.getIntArray(R.array.config_face_auth_wake_up_triggers).toSet()
     private val triggerFaceAuthOnWakeUpFrom: Set<Int>
@@ -65,11 +72,13 @@
         dumpManager.registerDumpable(this)
     }
 
-    fun shouldTriggerFaceAuthOnWakeUpFrom(@PowerManager.WakeReason pmWakeReason: Int): Boolean {
+    override fun shouldTriggerFaceAuthOnWakeUpFrom(
+        @PowerManager.WakeReason pmWakeReason: Int
+    ): Boolean {
         return triggerFaceAuthOnWakeUpFrom.contains(pmWakeReason)
     }
 
-    fun shouldTriggerFaceAuthOnWakeUpFrom(wakeReason: WakeSleepReason): Boolean =
+    override fun shouldTriggerFaceAuthOnWakeUpFrom(wakeReason: WakeSleepReason): Boolean =
         wakeSleepReasonsToTriggerFaceAuth.contains(wakeReason)
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
@@ -87,3 +96,8 @@
             ?: default
     }
 }
+
+@Module
+interface FaceWakeUpTriggersConfigModule {
+    @Binds fun repository(impl: FaceWakeUpTriggersConfigImpl): FaceWakeUpTriggersConfig
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/NoopDeviceEntryFaceAuthRepository.kt
similarity index 68%
rename from packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
rename to packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/NoopDeviceEntryFaceAuthRepository.kt
index f4a74f0..6695b182 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/NoopDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/NoopDeviceEntryFaceAuthRepository.kt
@@ -1,25 +1,25 @@
 /*
- *   Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
- *   Licensed under the Apache License, Version 2.0 (the "License");
- *   you may not use this file except in compliance with the License.
- *   You may obtain a copy of the License at
+ * 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
+ *      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.
+ * 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.data.repository
+package com.android.systemui.deviceentry.data.repository
 
-import com.android.keyguard.FaceAuthUiEvent
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
index 1a6bd04..5699176 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt
@@ -18,13 +18,14 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.shared.DeviceEntryBiometricMode
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
 import javax.inject.Inject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 
@@ -62,7 +63,9 @@
     val faceOnlyFaceFailure: Flow<FailedFaceAuthenticationStatus> =
         faceOnly.flatMapLatest { faceOnly ->
             if (faceOnly) {
-                deviceEntryFaceAuthInteractor.faceFailure
+                deviceEntryFaceAuthInteractor.authenticationStatus.filterIsInstance<
+                    FailedFaceAuthenticationStatus
+                >()
             } else {
                 emptyFlow()
             }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
index 70716c6..99bd25b 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,19 +16,80 @@
 
 package com.android.systemui.deviceentry.domain.interactor
 
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
-import javax.inject.Inject
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.filterIsInstance
 
-@SysUISingleton
-class DeviceEntryFaceAuthInteractor
-@Inject
-constructor(
-    repository: DeviceEntryFaceAuthRepository,
-) {
-    val faceFailure: Flow<FailedFaceAuthenticationStatus> =
-        repository.authenticationStatus.filterIsInstance<FailedFaceAuthenticationStatus>()
+/**
+ * Interactor that exposes API to get the face authentication status and handle any events that can
+ * cause face authentication to run for device entry.
+ */
+interface DeviceEntryFaceAuthInteractor {
+
+    /** Current authentication status */
+    val authenticationStatus: Flow<FaceAuthenticationStatus>
+
+    /** Current detection status */
+    val detectionStatus: Flow<FaceDetectionStatus>
+
+    /** Can face auth be run right now */
+    fun canFaceAuthRun(): Boolean
+
+    /** Whether face auth is currently running or not. */
+    fun isRunning(): Boolean
+
+    /** Whether face auth is in lock out state. */
+    fun isLockedOut(): Boolean
+
+    /** Whether face auth is enrolled and enabled for the current user */
+    fun isFaceAuthEnabledAndEnrolled(): Boolean
+
+    /** Whether the current user is authenticated successfully with face auth */
+    fun isAuthenticated(): Boolean
+    /**
+     * Register listener for use from code that cannot use [authenticationStatus] or
+     * [detectionStatus]
+     */
+    fun registerListener(listener: FaceAuthenticationListener)
+
+    /** Unregister previously registered listener */
+    fun unregisterListener(listener: FaceAuthenticationListener)
+
+    fun onUdfpsSensorTouched()
+    fun onAssistantTriggeredOnLockScreen()
+    fun onDeviceLifted()
+    fun onQsExpansionStared()
+    fun onNotificationPanelClicked()
+    fun onSwipeUpOnBouncer()
+    fun onPrimaryBouncerUserInput()
+    fun onAccessibilityAction()
+    fun onWalletLaunched()
+
+    /** Whether face auth is considered class 3 */
+    fun isFaceAuthStrong(): Boolean
+}
+
+/**
+ * Listener that can be registered with the [DeviceEntryFaceAuthInteractor] to receive updates about
+ * face authentication & detection updates.
+ *
+ * This is present to make it easier for use the new face auth API for code that cannot use
+ * [DeviceEntryFaceAuthInteractor.authenticationStatus] or
+ * [DeviceEntryFaceAuthInteractor.detectionStatus] flows.
+ */
+interface FaceAuthenticationListener {
+    /** Receive face isAuthenticated updates */
+    fun onAuthenticatedChanged(isAuthenticated: Boolean)
+
+    /** Receive face authentication status updates */
+    fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus)
+
+    /** Receive status updates whenever face detection runs */
+    fun onDetectionStatusChanged(status: FaceDetectionStatus)
+
+    fun onLockoutStateChanged(isLockedOut: Boolean)
+
+    fun onRunningStateChanged(isRunning: Boolean)
+
+    fun onAuthEnrollmentStateChanged(enrolled: Boolean)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index f6a9570..2680328 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -20,8 +20,8 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.scene.domain.interactor.SceneInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
new file mode 100644
index 0000000..3b94166
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+
+/**
+ * Implementation of the interactor that noops all face auth operations.
+ *
+ * This is required for SystemUI variants that do not support face authentication but still inject
+ * other SysUI components that depend on [DeviceEntryFaceAuthInteractor]
+ */
+@SysUISingleton
+class NoopDeviceEntryFaceAuthInteractor @Inject constructor() : DeviceEntryFaceAuthInteractor {
+    override val authenticationStatus: Flow<FaceAuthenticationStatus>
+        get() = emptyFlow()
+    override val detectionStatus: Flow<FaceDetectionStatus>
+        get() = emptyFlow()
+
+    override fun canFaceAuthRun(): Boolean = false
+
+    override fun isRunning(): Boolean = false
+
+    override fun isLockedOut(): Boolean = false
+
+    override fun isFaceAuthEnabledAndEnrolled(): Boolean = false
+
+    override fun isFaceAuthStrong(): Boolean = false
+
+    override fun isAuthenticated(): Boolean = false
+
+    override fun registerListener(listener: FaceAuthenticationListener) {}
+
+    override fun unregisterListener(listener: FaceAuthenticationListener) {}
+
+    override fun onUdfpsSensorTouched() {}
+
+    override fun onAssistantTriggeredOnLockScreen() {}
+
+    override fun onDeviceLifted() {}
+
+    override fun onQsExpansionStared() {}
+
+    override fun onNotificationPanelClicked() {}
+
+    override fun onSwipeUpOnBouncer() {}
+    override fun onPrimaryBouncerUserInput() {}
+    override fun onAccessibilityAction() {}
+    override fun onWalletLaunched() = Unit
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
similarity index 94%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
index e3f4739..98130eb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt
@@ -14,14 +14,12 @@
  *   limitations under the License.
  */
 
-package com.android.systemui.keyguard.domain.interactor
+package com.android.systemui.deviceentry.domain.interactor
 
 import android.app.trust.TrustManager
 import android.content.Context
 import android.hardware.biometrics.BiometricFaceConstants
 import android.hardware.biometrics.BiometricSourceType
-import com.android.keyguard.FaceAuthUiEvent
-import com.android.keyguard.FaceWakeUpTriggersConfig
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.CoreStartable
 import com.android.systemui.biometrics.data.repository.FacePropertyRepository
@@ -32,11 +30,14 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
-import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -65,7 +66,7 @@
  * SystemUI Keyguard.
  */
 @SysUISingleton
-class SystemUIKeyguardFaceAuthInteractor
+class SystemUIDeviceEntryFaceAuthInteractor
 @Inject
 constructor(
     private val context: Context,
@@ -84,7 +85,7 @@
     private val powerInteractor: PowerInteractor,
     private val biometricSettingsRepository: BiometricSettingsRepository,
     private val trustManager: TrustManager,
-) : CoreStartable, KeyguardFaceAuthInteractor {
+) : CoreStartable, DeviceEntryFaceAuthInteractor {
 
     private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
 
@@ -310,7 +311,7 @@
     }
 
     companion object {
-        const val TAG = "KeyguardFaceAuthInteractor"
+        const val TAG = "DeviceEntryFaceAuthInteractor"
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt
similarity index 73%
rename from packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
rename to packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt
index 2abb7a4..ee220d5 100644
--- a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/FaceAuthReason.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,58 +14,55 @@
  * limitations under the License.
  */
 
-package com.android.keyguard
+package com.android.systemui.deviceentry.shared
 
 import android.annotation.StringDef
 import android.os.PowerManager
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
-import com.android.keyguard.FaceAuthApiRequestReason.Companion.ACCESSIBILITY_ACTION
-import com.android.keyguard.FaceAuthApiRequestReason.Companion.NOTIFICATION_PANEL_CLICKED
-import com.android.keyguard.FaceAuthApiRequestReason.Companion.PICK_UP_GESTURE_TRIGGERED
-import com.android.keyguard.FaceAuthApiRequestReason.Companion.QS_EXPANDED
-import com.android.keyguard.FaceAuthApiRequestReason.Companion.SWIPE_UP_ON_BOUNCER
-import com.android.keyguard.FaceAuthApiRequestReason.Companion.UDFPS_POINTER_DOWN
-import com.android.keyguard.InternalFaceAuthReasons.ALL_AUTHENTICATORS_REGISTERED
-import com.android.keyguard.InternalFaceAuthReasons.ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
-import com.android.keyguard.InternalFaceAuthReasons.ASSISTANT_VISIBILITY_CHANGED
-import com.android.keyguard.InternalFaceAuthReasons.AUTH_REQUEST_DURING_CANCELLATION
-import com.android.keyguard.InternalFaceAuthReasons.BIOMETRIC_ENABLED
-import com.android.keyguard.InternalFaceAuthReasons.CAMERA_LAUNCHED
-import com.android.keyguard.InternalFaceAuthReasons.DEVICE_WOKEN_UP_ON_REACH_GESTURE
-import com.android.keyguard.InternalFaceAuthReasons.DISPLAY_OFF
-import com.android.keyguard.InternalFaceAuthReasons.DREAM_STARTED
-import com.android.keyguard.InternalFaceAuthReasons.DREAM_STOPPED
-import com.android.keyguard.InternalFaceAuthReasons.ENROLLMENTS_CHANGED
-import com.android.keyguard.InternalFaceAuthReasons.FACE_AUTHENTICATED
-import com.android.keyguard.InternalFaceAuthReasons.FACE_AUTH_STOPPED_ON_USER_INPUT
-import com.android.keyguard.InternalFaceAuthReasons.FACE_CANCEL_NOT_RECEIVED
-import com.android.keyguard.InternalFaceAuthReasons.FACE_LOCKOUT_RESET
-import com.android.keyguard.InternalFaceAuthReasons.FINISHED_GOING_TO_SLEEP
-import com.android.keyguard.InternalFaceAuthReasons.FP_AUTHENTICATED
-import com.android.keyguard.InternalFaceAuthReasons.FP_LOCKED_OUT
-import com.android.keyguard.InternalFaceAuthReasons.GOING_TO_SLEEP
-import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_GOING_AWAY
-import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_INIT
-import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_OCCLUSION_CHANGED
-import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_RESET
-import com.android.keyguard.InternalFaceAuthReasons.KEYGUARD_VISIBILITY_CHANGED
-import com.android.keyguard.InternalFaceAuthReasons.NON_STRONG_BIOMETRIC_ALLOWED_CHANGED
-import com.android.keyguard.InternalFaceAuthReasons.OCCLUDING_APP_REQUESTED
-import com.android.keyguard.InternalFaceAuthReasons.POSTURE_CHANGED
-import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN
-import com.android.keyguard.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN
-import com.android.keyguard.InternalFaceAuthReasons.RETRY_AFTER_HW_UNAVAILABLE
-import com.android.keyguard.InternalFaceAuthReasons.STARTED_WAKING_UP
-import com.android.keyguard.InternalFaceAuthReasons.STRONG_AUTH_ALLOWED_CHANGED
-import com.android.keyguard.InternalFaceAuthReasons.TRUST_DISABLED
-import com.android.keyguard.InternalFaceAuthReasons.TRUST_ENABLED
-import com.android.keyguard.InternalFaceAuthReasons.USER_SWITCHING
+import com.android.systemui.deviceentry.shared.FaceAuthApiRequestReason.Companion.ACCESSIBILITY_ACTION
+import com.android.systemui.deviceentry.shared.FaceAuthApiRequestReason.Companion.NOTIFICATION_PANEL_CLICKED
+import com.android.systemui.deviceentry.shared.FaceAuthApiRequestReason.Companion.PICK_UP_GESTURE_TRIGGERED
+import com.android.systemui.deviceentry.shared.FaceAuthApiRequestReason.Companion.QS_EXPANDED
+import com.android.systemui.deviceentry.shared.FaceAuthApiRequestReason.Companion.SWIPE_UP_ON_BOUNCER
+import com.android.systemui.deviceentry.shared.FaceAuthApiRequestReason.Companion.UDFPS_POINTER_DOWN
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.ALL_AUTHENTICATORS_REGISTERED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.ALTERNATE_BIOMETRIC_BOUNCER_SHOWN
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.ASSISTANT_VISIBILITY_CHANGED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.AUTH_REQUEST_DURING_CANCELLATION
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.BIOMETRIC_ENABLED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.CAMERA_LAUNCHED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.DEVICE_WOKEN_UP_ON_REACH_GESTURE
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.DISPLAY_OFF
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.DREAM_STARTED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.DREAM_STOPPED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.ENROLLMENTS_CHANGED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.FACE_AUTHENTICATED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.FACE_AUTH_STOPPED_ON_USER_INPUT
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.FACE_CANCEL_NOT_RECEIVED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.FACE_LOCKOUT_RESET
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.FINISHED_GOING_TO_SLEEP
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.FP_AUTHENTICATED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.FP_LOCKED_OUT
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.GOING_TO_SLEEP
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.KEYGUARD_GOING_AWAY
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.KEYGUARD_INIT
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.KEYGUARD_OCCLUSION_CHANGED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.KEYGUARD_RESET
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.KEYGUARD_VISIBILITY_CHANGED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.NON_STRONG_BIOMETRIC_ALLOWED_CHANGED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.OCCLUDING_APP_REQUESTED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.POSTURE_CHANGED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.PRIMARY_BOUNCER_SHOWN_OR_WILL_BE_SHOWN
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.RETRY_AFTER_HW_UNAVAILABLE
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.STARTED_WAKING_UP
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.STRONG_AUTH_ALLOWED_CHANGED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.TRUST_DISABLED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.TRUST_ENABLED
+import com.android.systemui.deviceentry.shared.InternalFaceAuthReasons.USER_SWITCHING
 
-/**
- * List of reasons why face auth is requested by clients through
- * [KeyguardUpdateMonitor.requestFaceAuth].
- */
+/** List of reasons why face auth is requested by clients. */
 @Retention(AnnotationRetention.SOURCE)
 @StringDef(
     SWIPE_UP_ON_BOUNCER,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/FaceAuthenticationModels.kt
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
rename to packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/FaceAuthenticationModels.kt
index 3de3666..f006b34 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/FaceAuthenticationModels.kt
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.keyguard.shared.model
+package com.android.systemui.deviceentry.shared.model
 
 import android.hardware.face.FaceManager
 import android.os.SystemClock.elapsedRealtime
 
 /**
  * Authentication status provided by
- * [com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository]
+ * [com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository]
  */
 sealed class FaceAuthenticationStatus
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index bb0c273..38c7c6a 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -444,29 +444,10 @@
     // TODO(b/254512728): Tracking Bug
     @JvmField val NEW_BACK_AFFORDANCE = releasedFlag("new_back_affordance")
 
-    // TODO(b/255854141): Tracking Bug
-    @JvmField
-    val WM_ENABLE_PREDICTIVE_BACK_SYSUI =
-        unreleasedFlag("persist.wm.debug.predictive_back_sysui_enable", teamfood = true)
 
     // TODO(b/270987164): Tracking Bug
     @JvmField val TRACKPAD_GESTURE_FEATURES = releasedFlag("trackpad_gesture_features")
 
-    // TODO(b/263826204): Tracking Bug
-    @JvmField
-    val WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM =
-        unreleasedFlag("persist.wm.debug.predictive_back_bouncer_anim", teamfood = true)
-
-    // TODO(b/238475428): Tracking Bug
-    @JvmField
-    val WM_SHADE_ALLOW_BACK_GESTURE =
-        sysPropBooleanFlag("persist.wm.debug.shade_allow_back_gesture", default = false)
-
-    // TODO(b/238475428): Tracking Bug
-    @JvmField
-    val WM_SHADE_ANIMATE_BACK_GESTURE =
-        unreleasedFlag("persist.wm.debug.shade_animate_back_gesture", teamfood = false)
-
     // TODO(b/265639042): Tracking Bug
     @JvmField
     val WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index aa4c88a..e23ec89 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -403,6 +403,7 @@
     public static final int INDICATION_TYPE_REVERSE_CHARGING = 10;
     public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE = 11;
     public static final int INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP = 12;
+    public static final int INDICATION_IS_DISMISSIBLE = 13;
 
     @IntDef({
             INDICATION_TYPE_NONE,
@@ -417,7 +418,8 @@
             INDICATION_TYPE_USER_LOCKED,
             INDICATION_TYPE_REVERSE_CHARGING,
             INDICATION_TYPE_BIOMETRIC_MESSAGE,
-            INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP
+            INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+            INDICATION_IS_DISMISSIBLE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface IndicationType{}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index edf9648..e2ab20e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -593,6 +593,13 @@
             mKeyguardViewMediator.doKeyguardTimeout(options);
         }
 
+        // Binder interface
+        public void showDismissibleKeyguard() {
+            trace("showDismissibleKeyguard");
+            checkPermission();
+            mKeyguardViewMediator.showDismissibleKeyguard();
+        }
+
         @Override // Binder interface
         public void setSwitchingUser(boolean switching) {
             trace("setSwitchingUser switching=" + switching);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d7a1906..a34730e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -107,6 +107,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.app.animation.Interpolators;
+import com.android.internal.foldables.FoldGracePeriodProvider;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.jank.InteractionJankMonitor.Configuration;
 import com.android.internal.logging.UiEventLogger;
@@ -311,6 +312,11 @@
      */
     public static final String OPTION_FORCE_SHOW = "force_show";
     public static final String SYS_BOOT_REASON_PROP = "sys.boot.reason.last";
+    /**
+     * Boolean option for showKeyguard, when set to true, can show the keyguard without immediately
+     * locking.
+     */
+    public static final String OPTION_SHOW_DISMISSIBLE = "show_dismissible";
     public static final String REBOOT_MAINLINE_UPDATE = "reboot,mainline_update";
     private final DreamOverlayStateController mDreamOverlayStateController;
     private final JavaAdapter mJavaAdapter;
@@ -1321,7 +1327,9 @@
     private DozeParameters mDozeParameters;
     private SelectedUserInteractor mSelectedUserInteractor;
     private KeyguardInteractor mKeyguardInteractor;
-
+    @VisibleForTesting
+    protected FoldGracePeriodProvider mFoldGracePeriodProvider =
+            new FoldGracePeriodProvider();
     private final KeyguardStateController mKeyguardStateController;
     private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
             new KeyguardStateController.Callback() {
@@ -1995,7 +2003,7 @@
                 mNeedToReshowWhenReenabled = false;
                 updateInputRestrictedLocked();
 
-                showLocked(null);
+                showKeyguard(null);
 
                 // block until we know the keyguard is done drawing (and post a message
                 // to unblock us after a timeout, so we don't risk blocking too long
@@ -2170,6 +2178,24 @@
     }
 
     /**
+     * Only available if the fold grace period feature is enabled.
+     * Used by PhoneWindowManager to show the keyguard immediately without locking the device.
+     * This method shows the keyguard whether there's a screen lock configured or not (including
+     * screen lock SWIPE or NONE).
+     * This must be safe to call from any thread and with any window manager locks held.
+     */
+    public void showDismissibleKeyguard() {
+        if (mFoldGracePeriodProvider.isEnabled()) {
+            Bundle showKeyguardUnlocked = new Bundle();
+            showKeyguardUnlocked.putBoolean(OPTION_SHOW_DISMISSIBLE, true);
+            showKeyguard(showKeyguardUnlocked);
+        } else {
+            Log.e(TAG, "fold grace period feature isn't enabled, but showKeyguard() method is"
+                    + " being called", new Throwable());
+        }
+    }
+
+    /**
      * Given the state of the keyguard, is the input restricted?
      * Input is restricted when the keyguard is showing, or when the keyguard
      * was suppressed by an app that disabled the keyguard or we haven't been provisioned yet.
@@ -2273,7 +2299,7 @@
         }
 
         if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen");
-        showLocked(options);
+        showKeyguard(options);
     }
 
     private void lockProfile(int userId) {
@@ -2335,18 +2361,18 @@
     }
 
     /**
-     * Send message to keyguard telling it to show itself
+     * Send message to keyguard telling it to show itself.
      * @see #handleShow
      */
-    private void showLocked(Bundle options) {
-        Trace.beginSection("KeyguardViewMediator#showLocked acquiring mShowKeyguardWakeLock");
-        if (DEBUG) Log.d(TAG, "showLocked");
+    private void showKeyguard(Bundle options) {
+        Trace.beginSection("KeyguardViewMediator#showKeyguard acquiring mShowKeyguardWakeLock");
+        if (DEBUG) Log.d(TAG, "showKeyguard");
         // ensure we stay awake until we are finished displaying the keyguard
         mShowKeyguardWakeLock.acquire();
         Message msg = mHandler.obtainMessage(SHOW, options);
         // Treat these messages with priority - This call can originate from #doKeyguardTimeout,
-        // meaning the device should lock as soon as possible and not wait for other messages on
-        // the thread to process first.
+        // meaning the device may lock, so it shouldn't wait for other messages on the thread to
+        // process first.
         mHandler.sendMessageAtFrontOfQueue(msg);
         Trace.endSection();
     }
@@ -2724,13 +2750,18 @@
     }
 
     /**
-     * Handle message sent by {@link #showLocked}.
+     * Handle message sent by {@link #showKeyguard}.
      * @see #SHOW
      */
     private void handleShow(Bundle options) {
         Trace.beginSection("KeyguardViewMediator#handleShow");
+        final boolean showUnlocked = options != null
+                && options.getBoolean(OPTION_SHOW_DISMISSIBLE, false);
         final int currentUser = mSelectedUserInteractor.getSelectedUserId();
-        if (mLockPatternUtils.isSecure(currentUser)) {
+        if (showUnlocked) {
+            // tell KeyguardUpdateMonitor to keep the device unlocked until the next lock signal
+            mUpdateMonitor.tryForceIsDismissibleKeyguard();
+        } else if (mLockPatternUtils.isSecure(currentUser)) {
             mLockPatternUtils.getDevicePolicyManager().reportKeyguardSecured(currentUser);
         }
         synchronized (KeyguardViewMediator.this) {
@@ -2755,7 +2786,7 @@
                         + ", which means we're showing in the middle of hiding.");
             }
 
-            // Force if we we're showing in the middle of unlocking, to ensure we end up in the
+            // Force if we're showing in the middle of unlocking, to ensure we end up in the
             // correct state.
             setShowingLocked(true, hidingOrGoingAway /* force */);
             mHiding = false;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
index d8eb81c..f29b657 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
@@ -16,10 +16,10 @@
 
 package com.android.systemui.keyguard.dagger
 
-import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.data.repository.NoopDeviceEntryFaceAuthRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
-import com.android.systemui.keyguard.domain.interactor.NoopKeyguardFaceAuthInteractor
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.data.repository.NoopDeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.NoopDeviceEntryFaceAuthInteractor
 import dagger.Binds
 import dagger.Module
 
@@ -33,7 +33,9 @@
 @Module
 interface KeyguardFaceAuthNotSupportedModule {
     @Binds
-    fun keyguardFaceAuthInteractor(impl: NoopKeyguardFaceAuthInteractor): KeyguardFaceAuthInteractor
+    fun keyguardFaceAuthInteractor(
+        impl: NoopDeviceEntryFaceAuthInteractor
+    ): DeviceEntryFaceAuthInteractor
 
     @Binds
     fun deviceEntryFaceAuthRepository(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
index ee0eb2d..b373f85 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
@@ -19,8 +19,10 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
-import com.android.systemui.keyguard.domain.interactor.SystemUIKeyguardFaceAuthInteractor
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepositoryImpl
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.SystemUIDeviceEntryFaceAuthInteractor
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
 import dagger.Binds
@@ -38,13 +40,13 @@
 
     @Binds
     @IntoMap
-    @ClassKey(SystemUIKeyguardFaceAuthInteractor::class)
-    fun bind(impl: SystemUIKeyguardFaceAuthInteractor): CoreStartable
+    @ClassKey(SystemUIDeviceEntryFaceAuthInteractor::class)
+    fun bind(impl: SystemUIDeviceEntryFaceAuthInteractor): CoreStartable
 
     @Binds
     fun keyguardFaceAuthInteractor(
-        impl: SystemUIKeyguardFaceAuthInteractor
-    ): KeyguardFaceAuthInteractor
+        impl: SystemUIDeviceEntryFaceAuthInteractor
+    ): DeviceEntryFaceAuthInteractor
 
     companion object {
         @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
deleted file mode 100644
index 046916a..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
+++ /dev/null
@@ -1,95 +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.domain.interactor
-
-import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
-import kotlinx.coroutines.flow.Flow
-
-/**
- * Interactor that exposes API to get the face authentication status and handle any events that can
- * cause face authentication to run.
- */
-interface KeyguardFaceAuthInteractor {
-
-    /** Current authentication status */
-    val authenticationStatus: Flow<FaceAuthenticationStatus>
-
-    /** Current detection status */
-    val detectionStatus: Flow<FaceDetectionStatus>
-
-    /** Can face auth be run right now */
-    fun canFaceAuthRun(): Boolean
-
-    /** Whether face auth is currently running or not. */
-    fun isRunning(): Boolean
-
-    /** Whether face auth is in lock out state. */
-    fun isLockedOut(): Boolean
-
-    /** Whether face auth is enrolled and enabled for the current user */
-    fun isFaceAuthEnabledAndEnrolled(): Boolean
-
-    /** Whether the current user is authenticated successfully with face auth */
-    fun isAuthenticated(): Boolean
-    /**
-     * Register listener for use from code that cannot use [authenticationStatus] or
-     * [detectionStatus]
-     */
-    fun registerListener(listener: FaceAuthenticationListener)
-
-    /** Unregister previously registered listener */
-    fun unregisterListener(listener: FaceAuthenticationListener)
-
-    fun onUdfpsSensorTouched()
-    fun onAssistantTriggeredOnLockScreen()
-    fun onDeviceLifted()
-    fun onQsExpansionStared()
-    fun onNotificationPanelClicked()
-    fun onSwipeUpOnBouncer()
-    fun onPrimaryBouncerUserInput()
-    fun onAccessibilityAction()
-    fun onWalletLaunched()
-
-    /** Whether face auth is considered class 3 */
-    fun isFaceAuthStrong(): Boolean
-}
-
-/**
- * Listener that can be registered with the [KeyguardFaceAuthInteractor] to receive updates about
- * face authentication & detection updates.
- *
- * This is present to make it easier for use the new face auth API for code that cannot use
- * [KeyguardFaceAuthInteractor.authenticationStatus] or [KeyguardFaceAuthInteractor.detectionStatus]
- * flows.
- */
-interface FaceAuthenticationListener {
-    /** Receive face isAuthenticated updates */
-    fun onAuthenticatedChanged(isAuthenticated: Boolean)
-
-    /** Receive face authentication status updates */
-    fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus)
-
-    /** Receive status updates whenever face detection runs */
-    fun onDetectionStatusChanged(status: FaceDetectionStatus)
-
-    fun onLockoutStateChanged(isLockedOut: Boolean)
-
-    fun onRunningStateChanged(isRunning: Boolean)
-
-    fun onAuthEnrollmentStateChanged(enrolled: Boolean)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
deleted file mode 100644
index cd6ab31..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
+++ /dev/null
@@ -1,69 +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.domain.interactor
-
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.emptyFlow
-
-/**
- * Implementation of the interactor that noops all face auth operations.
- *
- * This is required for SystemUI variants that do not support face authentication but still inject
- * other SysUI components that depend on [KeyguardFaceAuthInteractor]
- */
-@SysUISingleton
-class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInteractor {
-    override val authenticationStatus: Flow<FaceAuthenticationStatus>
-        get() = emptyFlow()
-    override val detectionStatus: Flow<FaceDetectionStatus>
-        get() = emptyFlow()
-
-    override fun canFaceAuthRun(): Boolean = false
-
-    override fun isRunning(): Boolean = false
-
-    override fun isLockedOut(): Boolean = false
-
-    override fun isFaceAuthEnabledAndEnrolled(): Boolean = false
-
-    override fun isFaceAuthStrong(): Boolean = false
-
-    override fun isAuthenticated(): Boolean = false
-
-    override fun registerListener(listener: FaceAuthenticationListener) {}
-
-    override fun unregisterListener(listener: FaceAuthenticationListener) {}
-
-    override fun onUdfpsSensorTouched() {}
-
-    override fun onAssistantTriggeredOnLockScreen() {}
-
-    override fun onDeviceLifted() {}
-
-    override fun onQsExpansionStared() {}
-
-    override fun onNotificationPanelClicked() {}
-
-    override fun onSwipeUpOnBouncer() {}
-    override fun onPrimaryBouncerUserInput() {}
-    override fun onAccessibilityAction() {}
-    override fun onWalletLaunched() = Unit
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SysUiFaceAuthenticateOptions.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SysUiFaceAuthenticateOptions.kt
index 942cd60..b3d0f918 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SysUiFaceAuthenticateOptions.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SysUiFaceAuthenticateOptions.kt
@@ -32,7 +32,7 @@
 import android.os.PowerManager.WAKE_REASON_UNKNOWN
 import android.util.Log
 import com.android.internal.logging.UiEventLogger
-import com.android.keyguard.FaceAuthUiEvent
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
 
 /**
  * Wrapper for [FaceAuthenticateOptions] to convert SystemUI values to their corresponding value in
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
index 5344696b..24d0602 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
@@ -30,11 +30,11 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
 import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
-import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeClockSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeMediaSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeSmartspaceSection
 import com.android.systemui.util.kotlin.getOrNull
 import java.util.Optional
 import javax.inject.Inject
@@ -62,7 +62,7 @@
     aodNotificationIconsSection: AodNotificationIconsSection,
     aodBurnInSection: AodBurnInSection,
     communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
-    smartspaceSection: SmartspaceSection,
+    smartspaceSection: SplitShadeSmartspaceSection,
     clockSection: SplitShadeClockSection,
     mediaSection: SplitShadeMediaSection,
 ) : KeyguardBlueprint {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt
new file mode 100644
index 0000000..8728ada
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
+import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
+import javax.inject.Inject
+
+/*
+ * We need this class for the splitShadeBlueprint so `addViews` and `removeViews` will be called
+ * when switching to and from splitShade.
+ */
+class SplitShadeSmartspaceSection
+@Inject
+constructor(
+    keyguardClockViewModel: KeyguardClockViewModel,
+    keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
+    context: Context,
+    smartspaceController: LockscreenSmartspaceController,
+    keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
+) :
+    SmartspaceSection(
+        keyguardClockViewModel,
+        keyguardSmartspaceViewModel,
+        context,
+        smartspaceController,
+        keyguardUnlockAnimationController,
+    )
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index 3c2facb..9e6c552 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -2,9 +2,9 @@
 
 import android.hardware.face.FaceManager
 import android.hardware.face.FaceSensorPropertiesInternal
-import com.android.keyguard.FaceAuthUiEvent
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
 import com.android.systemui.log.core.LogLevel.DEBUG
 import com.android.systemui.log.dagger.FaceAuthLog
 import com.android.systemui.power.shared.model.WakeSleepReason
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 1e67771..24cb8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -20,6 +20,7 @@
 
 import com.android.systemui.common.data.repository.PackageChangeRepository;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepositoryImpl;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.LogBufferFactory;
 import com.android.systemui.log.LogcatEchoTracker;
@@ -462,7 +463,7 @@
 
     /**
      * Provides a {@link LogBuffer} for use by
-     * {@link com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepositoryImpl}.
+     * {@link DeviceEntryFaceAuthRepositoryImpl}.
      */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 3e50dd3..ac0bd29 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -49,6 +49,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
@@ -231,32 +232,39 @@
         if (!collapsedView && mQsTileRevealController != null) {
             mQsTileRevealController.updateRevealedTiles(tiles);
         }
-        boolean shouldChange = false;
+        boolean shouldChangeAll = false;
+        // If the new tiles are a prefix of the old tiles, we delete the extra tiles (from the old).
+        // If not (even if they share a prefix) we remove all and add all the new ones.
         if (tiles.size() <= mRecords.size()) {
             int i = 0;
+            // Iterate through the requested tiles and check if they are the same as the existing
+            // tiles.
             for (QSTile tile : tiles) {
                 if (tile != mRecords.get(i).tile) {
-                    shouldChange = true;
+                    shouldChangeAll = true;
                     break;
                 }
                 i++;
             }
 
-            // If the first tiles are the same as the new ones, remove any extras.
-            if (!shouldChange) {
-                while (i < mRecords.size()) {
-                    QSPanelControllerBase.TileRecord record = mRecords.get(i);
+            // If the first tiles are the same as the new ones, we reuse them and remove any extra
+            // tiles.
+            if (!shouldChangeAll && i < mRecords.size()) {
+                List<TileRecord> extraRecords = mRecords.subList(i, mRecords.size());
+                for (QSPanelControllerBase.TileRecord record : extraRecords) {
                     mView.removeTile(record);
                     record.tile.removeCallback(record.callback);
-                    i++;
                 }
+                extraRecords.clear();
                 mCachedSpecs = getTilesSpecs();
             }
         } else {
-            shouldChange = true;
+            shouldChangeAll = true;
         }
 
-        if (shouldChange) {
+        // If we detected that the existing tiles are different than the requested tiles, clear them
+        // and add the new tiles.
+        if (shouldChangeAll) {
             for (QSPanelControllerBase.TileRecord record : mRecords) {
                 mView.removeTile(record);
                 record.tile.removeCallback(record.callback);
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 5abb4dd..c96651c 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -44,6 +44,7 @@
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
+import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.printSection
@@ -83,6 +84,7 @@
     private val powerInteractor: PowerInteractor,
     private val simBouncerInteractor: Lazy<SimBouncerInteractor>,
     private val authenticationInteractor: Lazy<AuthenticationInteractor>,
+    private val windowController: NotificationShadeWindowController,
 ) : CoreStartable {
 
     override fun start() {
@@ -92,6 +94,7 @@
             automaticallySwitchScenes()
             hydrateSystemUiState()
             collectFalsingSignals()
+            hydrateWindowFocus()
         } else {
             sceneLogger.logFrameworkEnabled(
                 isEnabled = false,
@@ -348,6 +351,20 @@
         }
     }
 
+    /** Keeps the focus state of the window view up-to-date. */
+    private fun hydrateWindowFocus() {
+        applicationScope.launch {
+            sceneInteractor.transitionState
+                .mapNotNull { transitionState ->
+                    (transitionState as? ObservableTransitionState.Idle)?.scene
+                }
+                .distinctUntilChanged()
+                .collect { sceneKey ->
+                    windowController.setNotificationShadeFocusable(sceneKey != SceneKey.Gone)
+                }
+        }
+    }
+
     private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
         sceneInteractor.changeScene(
             scene = SceneModel(targetSceneKey),
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index fb6bc38..a5c1cf1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -26,6 +26,7 @@
 import static com.android.keyguard.KeyguardClockSwitch.SMALL;
 import static com.android.systemui.Flags.keyguardBottomAreaRefactor;
 import static com.android.systemui.Flags.migrateClocksToBlueprint;
+import static com.android.systemui.Flags.predictiveBackAnimateShade;
 import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
 import static com.android.systemui.classifier.Classifier.GENERIC;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
@@ -121,6 +122,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.DumpsysTableLogger;
@@ -131,7 +133,6 @@
 import com.android.systemui.keyguard.KeyguardViewConfigurator;
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver;
@@ -327,7 +328,7 @@
     private final PulseExpansionHandler mPulseExpansionHandler;
     private final KeyguardBypassController mKeyguardBypassController;
     private final KeyguardUpdateMonitor mUpdateMonitor;
-    private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+    private final DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
     private final ConversationNotificationManager mConversationNotificationManager;
     private final AuthController mAuthController;
     private final MediaHierarchyManager mMediaHierarchyManager;
@@ -779,7 +780,7 @@
             ActiveNotificationsInteractor activeNotificationsInteractor,
             ShadeAnimationInteractor shadeAnimationInteractor,
             KeyguardViewConfigurator keyguardViewConfigurator,
-            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
+            DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
             SplitShadeStateController splitShadeStateController,
             PowerInteractor powerInteractor,
             KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm,
@@ -891,7 +892,7 @@
         mShadeHeaderController = shadeHeaderController;
         mLayoutInflater = layoutInflater;
         mFeatureFlags = featureFlags;
-        mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
+        mAnimateBack = predictiveBackAnimateShade();
         mTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
         mFalsingCollector = falsingCollector;
         mWakeUpCoordinator = coordinator;
@@ -936,7 +937,7 @@
         mScreenOffAnimationController = screenOffAnimationController;
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
         mLastDownEvents = new NPVCDownEventState.Buffer(MAX_DOWN_EVENT_BUFFER_SIZE);
-        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+        mDeviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor;
 
         int currentMode = navigationModeController.addListener(
                 mode -> mIsGestureNavigation = QuickStepContract.isGesturalMode(mode));
@@ -2978,9 +2979,9 @@
                     mShadeLog.v("onMiddleClicked on Keyguard, mDozingOnDown: false");
                     // Try triggering face auth, this "might" run. Check
                     // KeyguardUpdateMonitor#shouldListenForFace to see when face auth won't run.
-                    mKeyguardFaceAuthInteractor.onNotificationPanelClicked();
+                    mDeviceEntryFaceAuthInteractor.onNotificationPanelClicked();
 
-                    if (mKeyguardFaceAuthInteractor.canFaceAuthRun()) {
+                    if (mDeviceEntryFaceAuthInteractor.canFaceAuthRun()) {
                         mUpdateMonitor.requestActiveUnlock(
                                 ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
                                 "lockScreenEmptySpaceTap");
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 8397caa..1dff99d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -66,9 +66,9 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
@@ -152,7 +152,7 @@
     private final RecordingController mRecordingController;
     private final LockscreenGestureLogger mLockscreenGestureLogger;
     private final ShadeLogger mShadeLog;
-    private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+    private final DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
     private final CastController mCastController;
     private final SplitShadeStateController mSplitShadeStateController;
     private final InteractionJankMonitor mInteractionJankMonitor;
@@ -338,7 +338,7 @@
             InteractionJankMonitor interactionJankMonitor,
             ShadeLogger shadeLog,
             DumpManager dumpManager,
-            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor,
+            DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
             ShadeRepository shadeRepository,
             ShadeInteractor shadeInteractor,
             ActiveNotificationsInteractor activeNotificationsInteractor,
@@ -384,7 +384,7 @@
         mLockscreenGestureLogger = lockscreenGestureLogger;
         mMetricsLogger = metricsLogger;
         mShadeLog = shadeLog;
-        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+        mDeviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor;
         mCastController = castController;
         mInteractionJankMonitor = interactionJankMonitor;
         mShadeRepository = shadeRepository;
@@ -972,7 +972,7 @@
         // When expanding QS, let's authenticate the user if possible,
         // this will speed up notification actions.
         if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) {
-            mKeyguardFaceAuthInteractor.onQsExpansionStared();
+            mDeviceEntryFaceAuthInteractor.onQsExpansionStared();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 08415cb..d6d3e67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -31,6 +31,7 @@
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.IMPORTANT_MSG_MIN_DURATION;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_IS_DISMISSIBLE;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BIOMETRIC_MESSAGE;
@@ -178,6 +179,7 @@
     private KeyguardInteractor mKeyguardInteractor;
     private String mPersistentUnlockMessage;
     private String mAlignmentIndication;
+    private boolean mForceIsDismissible;
     private CharSequence mTrustGrantedIndication;
     private CharSequence mTransientIndication;
     private CharSequence mBiometricMessage;
@@ -442,6 +444,7 @@
 
         // Update persistent messages. The following methods should only be called if we're on the
         // lock screen:
+        updateForceIsDimissibileChanged();
         updateLockScreenDisclosureMsg();
         updateLockScreenOwnerInfo();
         updateLockScreenBatteryMsg(animate);
@@ -458,6 +461,22 @@
         updateDeviceEntryIndication(false);
     }
 
+    private void updateForceIsDimissibileChanged() {
+        if (mForceIsDismissible) {
+            mRotateTextViewController.updateIndication(
+                    INDICATION_IS_DISMISSIBLE,
+                    new KeyguardIndication.Builder()
+                            .setMessage(mContext.getResources().getString(
+                                    com.android.systemui.res.R.string.dismissible_keyguard_swipe)
+                            )
+                            .setTextColor(mInitialTextColorState)
+                            .build(),
+                    /* updateImmediately */ true);
+        } else {
+            mRotateTextViewController.hideIndication(INDICATION_IS_DISMISSIBLE);
+        }
+    }
+
     private void updateLockScreenDisclosureMsg() {
         if (mOrganizationOwnedDevice) {
             mBackgroundExecutor.execute(() -> {
@@ -1311,6 +1330,12 @@
         }
 
         @Override
+        public void onForceIsDismissibleChanged(boolean forceIsDismissible) {
+            mForceIsDismissible = forceIsDismissible;
+            updateDeviceEntryIndication(false);
+        }
+
+        @Override
         public void onTrustGrantedForCurrentUser(
                 boolean dismissKeyguard,
                 boolean newlyUnlocked,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
index 4b89615..9fdd0bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
@@ -18,7 +18,7 @@
 
 import android.os.PowerManager
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 6f5058c..2e54512 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -366,8 +366,7 @@
 
                 @Override
                 public void onStatePostChange() {
-                    mView.updateSensitiveness(mStatusBarStateController.goingToFullShade(),
-                            mLockscreenUserManager.isAnyProfilePublicMode());
+                    updateSensitivenessWithAnimation(mStatusBarStateController.goingToFullShade());
                     mView.onStatePostChange(mStatusBarStateController.fromShadeLocked());
                     if (!FooterViewRefactor.isEnabled()) {
                         updateImportantForAccessibility();
@@ -378,7 +377,7 @@
     private final UserChangedListener mLockscreenUserChangeListener = new UserChangedListener() {
         @Override
         public void onUserChanged(int userId) {
-            mView.updateSensitiveness(false, mLockscreenUserManager.isAnyProfilePublicMode());
+            updateSensitivenessWithAnimation(false);
             mHistoryEnabled = null;
             updateFooter();
         }
@@ -388,7 +387,11 @@
      * Recalculate sensitiveness without animation; called when waking up while keyguard occluded.
      */
     public void updateSensitivenessForOccludedWakeup() {
-        mView.updateSensitiveness(false, mLockscreenUserManager.isAnyProfilePublicMode());
+        updateSensitivenessWithAnimation(false);
+    }
+
+    private void updateSensitivenessWithAnimation(boolean animate) {
+        mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode());
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 57d49b2..6e3aabf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -31,6 +31,7 @@
 import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.Flags.predictiveBackSysui;
 
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
@@ -836,7 +837,7 @@
         mLightRevealScrim = lightRevealScrim;
 
         // Based on teamfood flag, turn predictive back dispatch on at runtime.
-        if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI)) {
+        if (predictiveBackSysui()) {
             mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 32b3ac2..9f08633 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -28,7 +28,7 @@
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.Assert
@@ -43,13 +43,13 @@
  */
 @SysUISingleton
 class KeyguardLiftController @Inject constructor(
-    private val context: Context,
-    private val statusBarStateController: StatusBarStateController,
-    private val asyncSensorManager: AsyncSensorManager,
-    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
-    private val dumpManager: DumpManager,
-    private val selectedUserInteractor: SelectedUserInteractor,
+        private val context: Context,
+        private val statusBarStateController: StatusBarStateController,
+        private val asyncSensorManager: AsyncSensorManager,
+        private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+        private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
+        private val dumpManager: DumpManager,
+        private val selectedUserInteractor: SelectedUserInteractor,
 ) : Dumpable, CoreStartable {
 
     private val pickupSensor = asyncSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE)
@@ -75,7 +75,7 @@
             // Not listening anymore since trigger events unregister themselves
             isListening = false
             updateListeningState()
-            keyguardFaceAuthInteractor.onDeviceLifted()
+            deviceEntryFaceAuthInteractor.onDeviceLifted()
             keyguardUpdateMonitor.requestActiveUnlock(
                 ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE,
                 "KeyguardLiftController")
@@ -113,7 +113,7 @@
         val onKeyguard = keyguardUpdateMonitor.isKeyguardVisible &&
                 !statusBarStateController.isDozing
 
-        val isFaceEnabled = keyguardFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()
+        val isFaceEnabled = deviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()
         val shouldListen = (onKeyguard || bouncerVisible) && isFaceEnabled
         if (shouldListen != isListening) {
             isListening = shouldListen
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 4999123..88347ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -18,6 +18,7 @@
 
 import static android.view.WindowInsets.Type.navigationBars;
 
+import static com.android.systemui.Flags.predictiveBackAnimateBouncer;
 import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
 import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -400,8 +401,7 @@
         mFoldAodAnimationController = sysUIUnfoldComponent
                 .map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
         mAlternateBouncerInteractor = alternateBouncerInteractor;
-        mIsBackAnimationEnabled =
-                featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM);
+        mIsBackAnimationEnabled = predictiveBackAnimateBouncer();
         mUdfpsOverlayInteractor = udfpsOverlayInteractor;
         mActivityStarter = activityStarter;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 16334d3..886010c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -493,5 +493,10 @@
         public void onEnabledTrustAgentsChanged(int userId) {
             update(false /* updateAlways */);
         }
+
+        @Override
+        public void onForceIsDismissibleChanged(boolean keepUnlockedOnFold) {
+            update(false /* updateAlways */);
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
index 5558aa7..111492c 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
@@ -43,7 +43,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.res.R;
@@ -69,7 +69,7 @@
     private final Executor mExecutor;
     private final Handler mHandler;
     private final FalsingManager mFalsingManager;
-    private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+    private final DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
     private FalsingCollector mFalsingCollector;
     private final UserTracker mUserTracker;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -94,7 +94,7 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             StatusBarKeyguardViewManager keyguardViewManager,
             UiEventLogger uiEventLogger,
-            KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) {
+            DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor) {
         mKeyguardStateController = keyguardStateController;
         mKeyguardDismissUtil = keyguardDismissUtil;
         mActivityStarter = activityStarter;
@@ -106,7 +106,7 @@
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mKeyguardViewManager = keyguardViewManager;
         mUiEventLogger = uiEventLogger;
-        mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
+        mDeviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor;
     }
 
     @Override
@@ -213,7 +213,7 @@
                 true,
                 Utils.getColorAttrDefaultColor(
                         this, com.android.internal.R.attr.colorAccentPrimary));
-        mKeyguardFaceAuthInteractor.onWalletLaunched();
+        mDeviceEntryFaceAuthInteractor.onWalletLaunched();
     }
 
     @Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index c5ce856..d8eb05a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -110,6 +110,7 @@
 import android.text.TextUtils;
 
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.foldables.FoldGracePeriodProvider;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.UiEventLogger;
@@ -120,16 +121,19 @@
 import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
 import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
 import com.android.settingslib.fuelgauge.BatteryStatus;
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig;
+import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigImpl;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
+import com.android.systemui.deviceentry.domain.interactor.FaceAuthenticationListener;
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus;
+import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus;
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
-import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus;
-import com.android.systemui.keyguard.shared.model.FaceDetectionStatus;
-import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
@@ -219,6 +223,8 @@
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
+    private FoldGracePeriodProvider mFoldGracePeriodProvider;
+    @Mock
     private TelephonyManager mTelephonyManager;
     @Mock
     private SensorPrivacyManager mSensorPrivacyManager;
@@ -261,7 +267,7 @@
     @Mock
     private SelectedUserInteractor mSelectedUserInteractor;
     @Mock
-    private KeyguardFaceAuthInteractor mFaceAuthInteractor;
+    private DeviceEntryFaceAuthInteractor mFaceAuthInteractor;
     @Captor
     private ArgumentCaptor<FaceAuthenticationListener> mFaceAuthenticationListener;
 
@@ -315,7 +321,7 @@
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.systemui.res.R.integer.config_face_auth_supported_posture,
                 DEVICE_POSTURE_UNKNOWN);
-        mFaceWakeUpTriggersConfig = new FaceWakeUpTriggersConfig(
+        mFaceWakeUpTriggersConfig = new FaceWakeUpTriggersConfigImpl(
                 mContext.getResources(),
                 mGlobalSettings,
                 mDumpManager
@@ -335,6 +341,7 @@
                         anyInt());
 
         mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
+        mKeyguardUpdateMonitor.mFoldGracePeriodProvider = mFoldGracePeriodProvider;
         setupBiometrics(mKeyguardUpdateMonitor);
         mKeyguardUpdateMonitor.setFaceAuthInteractor(mFaceAuthInteractor);
         verify(mFaceAuthInteractor).registerListener(mFaceAuthenticationListener.capture());
@@ -923,8 +930,7 @@
     @Test
     public void trustAgentHasTrust() {
         // WHEN user has trust
-        mKeyguardUpdateMonitor.onTrustChanged(true, true,
-                mSelectedUserInteractor.getSelectedUserId(), 0, null);
+        givenSelectedUserCanSkipBouncerFromTrustedState();
 
         // THEN user is considered as "having trust" and bouncer can be skipped
         Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
@@ -948,8 +954,7 @@
     @Test
     public void trustAgentHasTrust_fingerprintLockout() {
         // GIVEN user has trust
-        mKeyguardUpdateMonitor.onTrustChanged(true, true,
-                mSelectedUserInteractor.getSelectedUserId(), 0, null);
+        givenSelectedUserCanSkipBouncerFromTrustedState();
         Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(
                 mSelectedUserInteractor.getSelectedUserId()));
 
@@ -1992,6 +1997,43 @@
     }
 
     @Test
+    public void runFpDetectFlagDisabled_sideFps_keyguardDismissible_fingerprintAuthenticateRuns() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD);
+
+        // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
+        // will trigger updateBiometricListeningState();
+        clearInvocations(mFingerprintManager);
+        mKeyguardUpdateMonitor.resetBiometricListeningState();
+
+        // GIVEN the user can skip the bouncer
+        givenSelectedUserCanSkipBouncerFromTrustedState();
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+        mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
+        mTestableLooper.processAllMessages();
+
+        // WHEN verify authenticate runs
+        verifyFingerprintAuthenticateCall();
+    }
+
+    @Test
+    public void sideFps_keyguardDismissible_fingerprintDetectRuns() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_RUN_FINGERPRINT_DETECT_ON_DISMISSIBLE_KEYGUARD);
+        // Clear invocations, since previous setup (e.g. registering BiometricManager callbacks)
+        // will trigger updateBiometricListeningState();
+        clearInvocations(mFingerprintManager);
+        mKeyguardUpdateMonitor.resetBiometricListeningState();
+
+        // GIVEN the user can skip the bouncer
+        givenSelectedUserCanSkipBouncerFromTrustedState();
+        when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+        mKeyguardUpdateMonitor.dispatchStartedGoingToSleep(0 /* why */);
+        mTestableLooper.processAllMessages();
+
+        // WHEN verify detect runs
+        verifyFingerprintDetectCall();
+    }
+
+    @Test
     public void testFingerprintSensorProperties() throws RemoteException {
         mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
                 new ArrayList<>());
@@ -2096,6 +2138,35 @@
         verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FACE);
     }
 
+    private void givenSelectedUserCanSkipBouncerFromTrustedState() {
+        mKeyguardUpdateMonitor.onTrustChanged(true, true,
+                mSelectedUserInteractor.getSelectedUserId(), 0, null);
+    }
+
+    @Test
+    public void forceIsDismissibleKeyguard_foldingGracePeriodNotEnabled() {
+        when(mFoldGracePeriodProvider.isEnabled()).thenReturn(false);
+        primaryAuthNotRequiredByStrongAuthTracker();
+        mKeyguardUpdateMonitor.tryForceIsDismissibleKeyguard();
+        Assert.assertFalse(mKeyguardUpdateMonitor.forceIsDismissibleIsKeepingDeviceUnlocked());
+    }
+
+    @Test
+    public void forceIsDismissibleKeyguard() {
+        when(mFoldGracePeriodProvider.isEnabled()).thenReturn(true);
+        primaryAuthNotRequiredByStrongAuthTracker();
+        mKeyguardUpdateMonitor.tryForceIsDismissibleKeyguard();
+        Assert.assertTrue(mKeyguardUpdateMonitor.forceIsDismissibleIsKeepingDeviceUnlocked());
+    }
+
+    @Test
+    public void forceIsDismissibleKeyguard_respectsLockdown() {
+        when(mFoldGracePeriodProvider.isEnabled()).thenReturn(true);
+        userDeviceLockDown();
+        mKeyguardUpdateMonitor.tryForceIsDismissibleKeyguard();
+        Assert.assertFalse(mKeyguardUpdateMonitor.forceIsDismissibleIsKeepingDeviceUnlocked());
+    }
+
     private void verifyFingerprintAuthenticateNeverCalled() {
         verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
         verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 8693d5c..7c626a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -16,6 +16,9 @@
 
 package com.android.systemui.back.domain.interactor
 
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
 import android.view.ViewRootImpl
 import android.window.BackEvent
 import android.window.BackEvent.EDGE_LEFT
@@ -26,9 +29,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.statusbar.IStatusBarService
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -72,7 +74,6 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class BackActionInteractorTest : SysuiTestCase() {
     private val testScope = TestScope()
-    private val featureFlags = FakeFeatureFlags()
     private val executor = FakeExecutor(FakeSystemClock())
 
     @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
@@ -107,17 +108,17 @@
                 statusBarKeyguardViewManager,
                 shadeController,
                 notificationShadeWindowController,
-                windowRootViewVisibilityInteractor,
-                featureFlags,
+                windowRootViewVisibilityInteractor
             )
             .apply { this.setup(qsController, shadeViewController) }
     }
 
     private val powerInteractor = PowerInteractorFactory.create().powerInteractor
 
+    @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
     @Before
     fun setUp() {
-        featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false)
         whenever(notificationShadeWindowController.windowRootView).thenReturn(windowRootView)
         whenever(windowRootView.viewRootImpl).thenReturn(viewRootImpl)
         whenever(viewRootImpl.onBackInvokedDispatcher).thenReturn(onBackInvokedDispatcher)
@@ -229,9 +230,9 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE)
     fun animationFlagOff_onBackInvoked_keyguardNotified() {
         backActionInteractor.start()
-        featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false)
         windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
         powerInteractor.setAwakeForTest()
         val callback = getBackInvokedCallback()
@@ -243,8 +244,8 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE)
     fun animationFlagOn_onBackInvoked_keyguardNotified() {
-        featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
         backActionInteractor.start()
         windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
         powerInteractor.setAwakeForTest()
@@ -257,8 +258,8 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE)
     fun animationFlagOn_callbackIsAnimationCallback() {
-        featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
         backActionInteractor.start()
         windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
         powerInteractor.setAwakeForTest()
@@ -269,8 +270,8 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE)
     fun onBackProgressed_shadeCannotBeCollapsed_shadeViewControllerNotNotified() {
-        featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
         backActionInteractor.start()
         windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
         powerInteractor.setAwakeForTest()
@@ -284,8 +285,8 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_SHADE)
     fun onBackProgressed_shadeCanBeCollapsed_shadeViewControllerNotified() {
-        featureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true)
         backActionInteractor.start()
         windowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true)
         powerInteractor.setAwakeForTest()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index 1f7dd6d..c143bc0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -123,7 +123,6 @@
             udfpsControllerProvider,
             statusBarStateController,
             displayMetrics,
-            featureFlags,
             KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)),
             biometricUnlockController,
             lightRevealScrim,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
index 86b9b84..9b0e58d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegateTest.kt
@@ -22,7 +22,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.whenever
@@ -42,7 +42,7 @@
 class FaceAuthAccessibilityDelegateTest : SysuiTestCase() {
 
     @Mock private lateinit var hostView: View
-    @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+    @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
     private lateinit var underTest: FaceAuthAccessibilityDelegate
 
     @Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index 755fa02..54dbd04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -57,13 +57,13 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
 import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
 import com.android.systemui.log.SideFpsLogger
 import com.android.systemui.log.logcatLogBuffer
@@ -110,7 +110,7 @@
     @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
     @Mock private lateinit var activityTaskManager: ActivityTaskManager
     @Mock private lateinit var displayManager: DisplayManager
-    @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+    @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
     @Mock
     private lateinit var fingerprintInteractiveToAuthProvider: FingerprintInteractiveToAuthProvider
     @Mock private lateinit var fpsUnlockTracker: FpsUnlockTracker
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index bdca948..1fa60fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -55,13 +55,13 @@
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
 import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
 import com.android.systemui.log.SideFpsLogger
 import com.android.systemui.log.logcatLogBuffer
@@ -101,7 +101,7 @@
     @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
 
     @Mock private lateinit var activityTaskManager: ActivityTaskManager
-    @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+    @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
     @Mock
     private lateinit var fingerprintInteractiveToAuthProvider: FingerprintInteractiveToAuthProvider
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
index 45a426e..e796303 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt
@@ -36,13 +36,13 @@
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.flags.SystemPropertiesHelper
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.keyguard.shared.model.AuthenticationFlags
 import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown
 import com.android.systemui.res.R.string.kg_trust_agent_disabled
@@ -122,7 +122,7 @@
                 fakeTrustRepository,
                 testScope.backgroundScope,
                 mSelectedUserInteractor,
-                mock(KeyguardFaceAuthInteractor::class.java),
+                mock(DeviceEntryFaceAuthInteractor::class.java),
             )
         underTest =
             BouncerMessageInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index dacf23a..ee46f76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -32,9 +32,9 @@
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.bouncer.ui.BouncerViewDelegate
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
 import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
@@ -73,7 +73,7 @@
     @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
-    @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+    @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
     private lateinit var mainHandler: FakeHandler
     private lateinit var underTest: PrimaryBouncerInteractor
     private lateinit var resources: TestableResources
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
index 59bcf01..d5c3641 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
@@ -24,13 +24,13 @@
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor
+import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus
 import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
 import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
 import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.data.repository.powerRepository
 import com.android.systemui.power.shared.model.WakeSleepReason
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/FaceWakeUpTriggersConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigTest.kt
similarity index 91%
rename from packages/SystemUI/tests/src/com/android/keyguard/FaceWakeUpTriggersConfigTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigTest.kt
index 94c34a5..e9b4bbb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/FaceWakeUpTriggersConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.keyguard
+package com.android.systemui.deviceentry.data.repository
 
 import android.os.PowerManager
 import android.testing.AndroidTestingRunner
@@ -71,6 +71,6 @@
             wakeUpTriggers
         )
 
-        return FaceWakeUpTriggersConfig(mContext.getResources(), globalSettings, dumpManager)
+        return FaceWakeUpTriggersConfigImpl(mContext.resources, globalSettings, dumpManager)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
similarity index 93%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
index 769cf45..368d1d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorTest.kt
@@ -1,21 +1,20 @@
 /*
- *  Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
+ * 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
+ *      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.
- *
+ * 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.domain.interactor
+package com.android.systemui.deviceentry.domain.interactor
 
 import android.app.trust.TrustManager
 import android.content.pm.UserInfo
@@ -25,8 +24,6 @@
 import android.os.PowerManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.keyguard.FaceAuthUiEvent
-import com.android.keyguard.FaceWakeUpTriggersConfig
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.SysuiTestCase
@@ -42,6 +39,9 @@
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
+import com.android.systemui.deviceentry.shared.model.ErrorFaceAuthenticationStatus
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
@@ -49,7 +49,8 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.shared.model.ErrorFaceAuthenticationStatus
+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.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -87,9 +88,9 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
+class DeviceEntryFaceAuthInteractorTest : SysuiTestCase() {
 
-    private lateinit var underTest: SystemUIKeyguardFaceAuthInteractor
+    private lateinit var underTest: SystemUIDeviceEntryFaceAuthInteractor
     private lateinit var testScope: TestScope
     private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
     private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
@@ -133,7 +134,7 @@
         fakeBiometricSettingsRepository = FakeBiometricSettingsRepository()
 
         underTest =
-            SystemUIKeyguardFaceAuthInteractor(
+            SystemUIDeviceEntryFaceAuthInteractor(
                 mContext,
                 testScope.backgroundScope,
                 dispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/FaceAuthReasonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/shared/FaceAuthReasonTest.kt
similarity index 92%
rename from packages/SystemUI/tests/src/com/android/keyguard/FaceAuthReasonTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/deviceentry/shared/FaceAuthReasonTest.kt
index 68d0f41..ef89752 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/FaceAuthReasonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/shared/FaceAuthReasonTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.keyguard
+
+package com.android.systemui.deviceentry.shared
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index b38c9ec..b57cf53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -72,6 +72,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.foldables.FoldGracePeriodProvider;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.UiEventLogger;
@@ -324,6 +325,54 @@
 
     @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    public void showKeyguardAfterKeyguardNotEnabled() {
+        // GIVEN feature is enabled
+        final FoldGracePeriodProvider mockedFoldGracePeriodProvider =
+                mock(FoldGracePeriodProvider.class);
+        mViewMediator.mFoldGracePeriodProvider = mockedFoldGracePeriodProvider;
+        when(mockedFoldGracePeriodProvider.isEnabled()).thenReturn(true);
+
+        // GIVEN keyguard is not enabled and isn't showing
+        mViewMediator.onSystemReady();
+        mViewMediator.setKeyguardEnabled(false);
+        TestableLooper.get(this).processAllMessages();
+        captureKeyguardUpdateMonitorCallback();
+        assertFalse(mViewMediator.isShowingAndNotOccluded());
+
+        // WHEN showKeyguard is requested
+        mViewMediator.showDismissibleKeyguard();
+
+        // THEN keyguard is shown
+        TestableLooper.get(this).processAllMessages();
+        assertTrue(mViewMediator.isShowingAndNotOccluded());
+    }
+
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
+    public void showKeyguardAfterKeyguardNotEnabled_featureNotEnabled() {
+        // GIVEN feature is NOT enabled
+        final FoldGracePeriodProvider mockedFoldGracePeriodProvider =
+                mock(FoldGracePeriodProvider.class);
+        mViewMediator.mFoldGracePeriodProvider = mockedFoldGracePeriodProvider;
+        when(mockedFoldGracePeriodProvider.isEnabled()).thenReturn(false);
+
+        // GIVEN keyguard is not enabled and isn't showing
+        mViewMediator.onSystemReady();
+        mViewMediator.setKeyguardEnabled(false);
+        TestableLooper.get(this).processAllMessages();
+        captureKeyguardUpdateMonitorCallback();
+        assertFalse(mViewMediator.isShowingAndNotOccluded());
+
+        // WHEN showKeyguard is requested
+        mViewMediator.showDismissibleKeyguard();
+
+        // THEN keyguard is still NOT shown
+        TestableLooper.get(this).processAllMessages();
+        assertFalse(mViewMediator.isShowingAndNotOccluded());
+    }
+
+    @Test
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
     public void doNotHideKeyguard_whenLockdown_onKeyguardNotEnabledExternally() {
         // GIVEN keyguard is enabled and lockdown occurred so the keyguard is showing
         mViewMediator.onSystemReady();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index 027dfa1..c4df27c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
@@ -62,7 +63,7 @@
 class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
     @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
 
-    @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+    @Mock private lateinit var faceAuthInteractor: DeviceEntryFaceAuthInteractor
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index 1f245f1..7358b9d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -147,7 +147,7 @@
                     trustRepository,
                     testScope.backgroundScope,
                     mSelectedUserInteractor,
-                    keyguardFaceAuthInteractor = mock(),
+                    deviceEntryFaceAuthInteractor = mock(),
                 ),
                 AlternateBouncerInteractor(
                     statusBarStateController = mock(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 018fa9e..a92111e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -394,6 +394,7 @@
     public void setTiles_differentTiles_extraTileRemoved() {
         when(mQSHost.getTiles()).thenReturn(List.of(mQSTile, mOtherTile));
         mController.setTiles();
+        assertEquals(2, mController.mRecords.size());
 
         clearInvocations(mQSPanel);
 
@@ -402,6 +403,7 @@
 
         verify(mQSPanel, times(1)).removeTile(any());
         verify(mQSPanel, never()).addTile(any());
+        assertEquals(1, mController.mRecords.size());
     }
 
     @Test
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 2220756..3132767 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -86,6 +86,7 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.common.ui.view.LongPressHandlingView;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.dump.DumpManager;
@@ -98,7 +99,6 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -337,7 +337,7 @@
     protected ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
             mEmptySpaceClickListenerCaptor;
     @Mock protected ActivityStarter mActivityStarter;
-    @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
+    @Mock protected DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
     @Mock private JavaAdapter mJavaAdapter;
     @Mock private CastController mCastController;
     @Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
@@ -382,7 +382,6 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mFeatureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, false);
         mFeatureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false);
         mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false);
         mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false);
@@ -730,7 +729,7 @@
                 mActiveNotificationsInteractor,
                 mShadeAnimationInteractor,
                 mKeyguardViewConfigurator,
-                mKeyguardFaceAuthInteractor,
+                mDeviceEntryFaceAuthInteractor,
                 new ResourcesSplitShadeStateController(),
                 mPowerInteractor,
                 mKeyguardClockPositionAlgorithm,
@@ -800,7 +799,7 @@
                 mInteractionJankMonitor,
                 mShadeLog,
                 mDumpManager,
-                mKeyguardFaceAuthInteractor,
+                mDeviceEntryFaceAuthInteractor,
                 mShadeRepository,
                 mShadeInteractor,
                 mActiveNotificationsInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 3cbb9bb..2e8d46a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -1120,7 +1120,7 @@
         mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
         mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
 
-        verify(mKeyguardFaceAuthInteractor).onNotificationPanelClicked();
+        verify(mDeviceEntryFaceAuthInteractor).onNotificationPanelClicked();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index c21addc..ee7c6c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -62,7 +62,7 @@
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -257,7 +257,7 @@
                             FakeTrustRepository(),
                             testScope.backgroundScope,
                             mSelectedUserInteractor,
-                            mock(KeyguardFaceAuthInteractor::class.java)
+                            mock(DeviceEntryFaceAuthInteractor::class.java)
                         ),
                     facePropertyRepository = FakeFacePropertyRepository(),
                     deviceEntryFingerprintAuthRepository =
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 eb5633b..727a6c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -42,6 +42,7 @@
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlagsClassic;
@@ -55,7 +56,6 @@
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
@@ -378,7 +378,7 @@
                 mInteractionJankMonitor,
                 mShadeLogger,
                 mDumpManager,
-                mock(KeyguardFaceAuthInteractor.class),
+                mock(DeviceEntryFaceAuthInteractor.class),
                 mShadeRepository,
                 mShadeInteractor,
                 mActiveNotificationsInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index e339636..316f2b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -334,15 +334,8 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        // CentralSurfacesImpl's runtime flag check fails if the flag is absent.
-        // This value is unused, because test manifest is opted in.
-        mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_SYSUI, false);
         // Set default value to avoid IllegalStateException.
         mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false);
-        // For the Shade to respond to Back gesture, we must enable the event routing
-        mFeatureFlags.set(Flags.WM_SHADE_ALLOW_BACK_GESTURE, true);
-        // For the Shade to animate during the Back gesture, we must enable the animation flag.
-        mFeatureFlags.set(Flags.WM_SHADE_ANIMATE_BACK_GESTURE, true);
         mSetFlagsRule.enableFlags(FLAG_LIGHT_REVEAL_MIGRATION);
         // Turn AOD on and toggle feature flag for jank fixes
         mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 225ddb6..8dde935 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -37,6 +37,9 @@
 
 import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
 
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.service.trust.TrustAgentService;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -98,6 +101,7 @@
 import com.google.common.truth.Truth;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -165,6 +169,8 @@
     @Captor
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Before
     public void setUp() {
@@ -175,7 +181,6 @@
         when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
         when(mBouncerViewDelegate.getBackCallback()).thenReturn(mBouncerViewDelegateBackCallback);
         mFeatureFlags = new FakeFeatureFlags();
-        mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM, true);
         mFeatureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false);
         mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
         mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
@@ -584,6 +589,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER)
     public void testPredictiveBackCallback_registration() {
         /* verify that a predictive back callback is registered when the bouncer becomes visible */
         mBouncerExpansionCallback.onVisibilityChanged(true);
@@ -598,6 +604,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER)
     public void testPredictiveBackCallback_invocationHidesBouncer() {
         mBouncerExpansionCallback.onVisibilityChanged(true);
         /* capture the predictive back callback during registration */
@@ -615,6 +622,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER)
     public void testPredictiveBackCallback_noBackAnimationForFullScreenBouncer() {
         when(mKeyguardSecurityModel.getSecurityMode(anyInt()))
                 .thenReturn(KeyguardSecurityModel.SecurityMode.SimPin);
@@ -634,6 +642,7 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER)
     public void testPredictiveBackCallback_forwardsBackDispatches() {
         mBouncerExpansionCallback.onVisibilityChanged(true);
         /* capture the predictive back callback during registration */
diff --git a/packages/SystemUI/tests/utils/src/com/android/keyguard/TrustManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/keyguard/TrustManagerKosmos.kt
new file mode 100644
index 0000000..187935b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/keyguard/TrustManagerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.keyguard
+
+import android.app.trust.TrustManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.trustManager by Kosmos.Fixture { mock<TrustManager>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryKosmos.kt
new file mode 100644
index 0000000..6ef7419
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.facePropertyRepository by Fixture { FakeFacePropertyRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt
new file mode 100644
index 0000000..21cff0d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FaceWakeUpTriggersConfigKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig by
+    Kosmos.Fixture { FakeFaceWakeUpTriggersConfig() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeFaceWakeUpTriggersConfig.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeFaceWakeUpTriggersConfig.kt
new file mode 100644
index 0000000..af617b7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeFaceWakeUpTriggersConfig.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.deviceentry.data.repository
+
+import com.android.systemui.power.shared.model.WakeSleepReason
+
+class FakeFaceWakeUpTriggersConfig : FaceWakeUpTriggersConfig {
+    private val triggerFaceAuthOnWakeUpFrom: MutableSet<Int> = mutableSetOf()
+    private val wakeSleepReasonsToTriggerFaceAuth: MutableSet<WakeSleepReason> = mutableSetOf()
+    override fun shouldTriggerFaceAuthOnWakeUpFrom(pmWakeReason: Int): Boolean {
+        return triggerFaceAuthOnWakeUpFrom.contains(pmWakeReason)
+    }
+
+    override fun shouldTriggerFaceAuthOnWakeUpFrom(wakeReason: WakeSleepReason): Boolean {
+        return wakeSleepReasonsToTriggerFaceAuth.contains(wakeReason)
+    }
+
+    fun setTriggerFaceAuthOnWakeUpFrom(pmWakeReasons: Set<Int>) {
+        triggerFaceAuthOnWakeUpFrom.clear()
+        triggerFaceAuthOnWakeUpFrom.addAll(pmWakeReasons)
+
+        wakeSleepReasonsToTriggerFaceAuth.clear()
+        wakeSleepReasonsToTriggerFaceAuth.addAll(
+            pmWakeReasons.map { WakeSleepReason.fromPowerManagerWakeReason(it) }
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
index d2dff78..0b1fb40 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt
@@ -18,13 +18,45 @@
 
 package com.android.systemui.deviceentry.domain.interactor
 
+import android.content.applicationContext
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.keyguard.trustManager
+import com.android.systemui.biometrics.data.repository.facePropertyRepository
+import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.deviceentry.data.repository.faceWakeUpTriggersConfig
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.mockito.mock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
+val Kosmos.faceAuthLogger by Kosmos.Fixture { mock<FaceAuthenticationLogger>() }
 val Kosmos.deviceEntryFaceAuthInteractor by
     Kosmos.Fixture {
-        DeviceEntryFaceAuthInteractor(
+        SystemUIDeviceEntryFaceAuthInteractor(
+            context = applicationContext,
+            applicationScope = applicationCoroutineScope,
+            mainDispatcher = testDispatcher,
             repository = deviceEntryFaceAuthRepository,
+            primaryBouncerInteractor = { primaryBouncerInteractor },
+            alternateBouncerInteractor = alternateBouncerInteractor,
+            keyguardTransitionInteractor = keyguardTransitionInteractor,
+            faceAuthenticationLogger = faceAuthLogger,
+            keyguardUpdateMonitor = keyguardUpdateMonitor,
+            deviceEntryFingerprintAuthRepository = deviceEntryFingerprintAuthRepository,
+            userRepository = userRepository,
+            facePropertyRepository = facePropertyRepository,
+            faceWakeUpTriggersConfig = faceWakeUpTriggersConfig,
+            powerInteractor = powerInteractor,
+            biometricSettingsRepository = biometricSettingsRepository,
+            trustManager = trustManager,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryKosmos.kt
index 3d72967..599b5142 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.kosmos.Kosmos
 
 var Kosmos.deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository by
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index a1b6587..e96aeada 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -16,10 +16,11 @@
 
 package com.android.systemui.keyguard.data.repository
 
-import com.android.keyguard.FaceAuthUiEvent
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.FaceAuthenticationStatus
-import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.deviceentry.shared.FaceAuthUiEvent
+import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus
+import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
index 3cabf0c..5f5d428 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.bouncer.ui.BouncerView
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -73,7 +74,7 @@
                 trustRepository,
                 testScope.backgroundScope,
                 mock(SelectedUserInteractor::class.java),
-                mock(KeyguardFaceAuthInteractor::class.java),
+                mock(DeviceEntryFaceAuthInteractor::class.java),
             )
         val alternateBouncerInteractor =
             AlternateBouncerInteractor(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 9f71161..09ab655 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -52,20 +52,20 @@
 import com.android.systemui.communal.data.repository.FakeCommunalRepository
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalInteractorFactory
+import com.android.systemui.deviceentry.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository
 import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
 import com.android.systemui.doze.DozeLogger
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.data.repository.TrustRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.kosmos.Kosmos
@@ -266,14 +266,14 @@
 
     fun bouncerInteractor(
         authenticationInteractor: AuthenticationInteractor,
-        keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor = mock(),
+        deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor = mock(),
     ): BouncerInteractor {
         return BouncerInteractor(
             applicationScope = applicationScope(),
             applicationContext = context,
             repository = bouncerRepository,
             authenticationInteractor = authenticationInteractor,
-            keyguardFaceAuthInteractor = keyguardFaceAuthInteractor,
+            deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
             falsingInteractor = falsingInteractor(),
             powerInteractor = powerInteractor(),
             simBouncerInteractor = simBouncerInteractor,
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 6b0fdb5..cf414d1 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -240,10 +240,20 @@
         mEventInternal = Optional.of(new PresentationStatsEventInternal());
     }
 
+    /**
+     * Set request_id
+     */
     public void maybeSetRequestId(int requestId) {
         mEventInternal.ifPresent(event -> event.mRequestId = requestId);
     }
 
+    /**
+     * Set is_credential_request
+     */
+    public void maybeSetIsCredentialRequest(boolean isCredentialRequest) {
+        mEventInternal.ifPresent(event -> event.mIsCredentialRequest = isCredentialRequest);
+    }
+
     public void maybeSetNoPresentationEventReason(@NotShownReason int reason) {
         mEventInternal.ifPresent(event -> {
             if (event.mCountShown == 0) {
@@ -567,7 +577,8 @@
                     + " mSelectedDatasetPickedReason=" + event.mSelectedDatasetPickedReason
                     + " mDetectionPreference=" + event.mDetectionPreference
                     + " mFieldClassificationRequestId=" + event.mFieldClassificationRequestId
-                    + " mAppPackageUid=" + mCallingAppUid);
+                    + " mAppPackageUid=" + mCallingAppUid
+                    + " mIsCredentialRequest=" + event.mIsCredentialRequest);
         }
 
         // TODO(b/234185326): Distinguish empty responses from other no presentation reasons.
@@ -606,7 +617,8 @@
                 event.mSelectedDatasetPickedReason,
                 event.mDetectionPreference,
                 event.mFieldClassificationRequestId,
-                mCallingAppUid);
+                mCallingAppUid,
+                event.mIsCredentialRequest);
         mEventInternal = Optional.empty();
     }
 
@@ -640,6 +652,7 @@
         @DatasetPickedReason int mSelectedDatasetPickedReason = PICK_REASON_UNKNOWN;
         @DetectionPreference int mDetectionPreference = DETECTION_PREFER_UNKNOWN;
         int mFieldClassificationRequestId = -1;
+        boolean mIsCredentialRequest = false;
 
         PresentationStatsEventInternal() {}
     }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index a49f9db..007be05 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1290,7 +1290,9 @@
             Slog.v(TAG, "Requesting structure for request #" + ordinal + " ,requestId=" + requestId
                     + ", flags=" + flags);
         }
+        boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
         mPresentationStatsEventLogger.maybeSetRequestId(requestId);
+        mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
         mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
                 mFieldClassificationIdSnapshot);
         mFillRequestEventLogger.maybeSetRequestId(requestId);
@@ -4360,8 +4362,10 @@
                 }
 
                 if (viewState.getResponse() != null) {
+                    boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
                     FillResponse response = viewState.getResponse();
                     mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
+                    mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
                     mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
                             mFieldClassificationIdSnapshot);
                     mPresentationStatsEventLogger.maybeSetAvailableCount(
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
new file mode 100644
index 0000000..a7dbd1c
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import static android.os.UserHandle.getCallingUserId;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
+import android.companion.AssociationInfo;
+import android.companion.Flags;
+import android.companion.datatransfer.SystemDataTransferRequest;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.util.CollectionUtils;
+import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Predicate;
+
+@SuppressLint("LongLogTag")
+class BackupRestoreProcessor {
+    static final String TAG = "CDM_BackupRestoreProcessor";
+    private static final int BACKUP_AND_RESTORE_VERSION = 0;
+
+    @NonNull
+    private final CompanionDeviceManagerService mService;
+    @NonNull
+    private final PackageManagerInternal mPackageManager;
+    @NonNull
+    private final AssociationStoreImpl mAssociationStore;
+    @NonNull
+    private final PersistentDataStore mPersistentStore;
+    @NonNull
+    private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
+    @NonNull
+    private final AssociationRequestsProcessor mAssociationRequestsProcessor;
+
+    BackupRestoreProcessor(@NonNull CompanionDeviceManagerService service,
+                           @NonNull AssociationStoreImpl associationStore,
+                           @NonNull PersistentDataStore persistentStore,
+                           @NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
+                           @NonNull AssociationRequestsProcessor associationRequestsProcessor) {
+        mService = service;
+        mPackageManager = service.mPackageManagerInternal;
+        mAssociationStore = associationStore;
+        mPersistentStore = persistentStore;
+        mSystemDataTransferRequestStore = systemDataTransferRequestStore;
+        mAssociationRequestsProcessor = associationRequestsProcessor;
+    }
+
+    /**
+     * Generate CDM state payload to be backed up.
+     * Backup payload is formatted as following:
+     * | (4) payload version | (4) AssociationInfo length | AssociationInfo XML
+     * | (4) SystemDataTransferRequest length | SystemDataTransferRequest XML (without userId)|
+     */
+    byte[] getBackupPayload(int userId) {
+        // Persist state first to generate an up-to-date XML file
+        mService.persistStateForUser(userId);
+        byte[] associationsPayload = mPersistentStore.getBackupPayload(userId);
+        int associationsPayloadLength = associationsPayload.length;
+
+        // System data transfer requests are persisted up-to-date already
+        byte[] requestsPayload = mSystemDataTransferRequestStore.getBackupPayload(userId);
+        int requestsPayloadLength = requestsPayload.length;
+
+        int payloadSize = /* 3 integers */ 12
+                + associationsPayloadLength
+                + requestsPayloadLength;
+
+        return ByteBuffer.allocate(payloadSize)
+                .putInt(BACKUP_AND_RESTORE_VERSION)
+                .putInt(associationsPayloadLength)
+                .put(associationsPayload)
+                .putInt(requestsPayloadLength)
+                .put(requestsPayload)
+                .array();
+    }
+
+    /**
+     * Create new associations and system data transfer request consents using backed up payload.
+     */
+    void applyRestoredPayload(byte[] payload, int userId) {
+        ByteBuffer buffer = ByteBuffer.wrap(payload);
+
+        // Make sure that payload version matches current version to ensure proper deserialization
+        int version = buffer.getInt();
+        if (version != BACKUP_AND_RESTORE_VERSION) {
+            Slog.e(TAG, "Unsupported backup payload version");
+            return;
+        }
+
+        // Read the bytes containing backed-up associations
+        byte[] associationsPayload = new byte[buffer.getInt()];
+        buffer.get(associationsPayload);
+        final Set<AssociationInfo> restoredAssociations = new HashSet<>();
+        mPersistentStore.readStateFromPayload(associationsPayload, userId,
+                restoredAssociations, new HashMap<>());
+
+        // Read the bytes containing backed-up system data transfer requests user consent
+        byte[] requestsPayload = new byte[buffer.getInt()];
+        buffer.get(requestsPayload);
+        List<SystemDataTransferRequest> restoredRequestsForUser =
+                mSystemDataTransferRequestStore.readRequestsFromPayload(requestsPayload);
+
+        // Get a list of installed packages ahead of time.
+        List<ApplicationInfo> installedApps = mPackageManager.getInstalledApplications(
+                0, userId, getCallingUserId());
+
+        // Restored device may have a different user ID than the backed-up user's user-ID. Since
+        // association ID is dependent on the user ID, restored associations must account for
+        // this potential difference on their association IDs.
+        for (AssociationInfo restored : restoredAssociations) {
+            // Don't restore a revoked association. Since they weren't added to the device being
+            // restored in the first place, there is no need to worry about revoking a role that
+            // was never granted either.
+            if (restored.isRevoked()) {
+                continue;
+            }
+
+            // Filter restored requests for those that belong to the restored association.
+            List<SystemDataTransferRequest> restoredRequests = CollectionUtils.filter(
+                    restoredRequestsForUser, it -> it.getAssociationId() == restored.getId());
+
+            // Handle collision: If a local association belonging to the same package already exists
+            // and their tags match, then keep the local one in favor of creating a new association.
+            if (handleCollision(userId, restored, restoredRequests)) {
+                continue;
+            }
+
+            // Create a new association reassigned to this user and a valid association ID
+            final String packageName = restored.getPackageName();
+            final int newId = mService.getNewAssociationIdForPackage(userId, packageName);
+            AssociationInfo newAssociation =
+                    new AssociationInfo.Builder(newId, userId, packageName, restored)
+                            .build();
+
+            // Check if the companion app for this association is already installed, then do one
+            // of the following:
+            // (1) If the app is already installed, then go ahead and add this association and grant
+            // the role attached to this association to the app.
+            // (2) If the app isn't yet installed, then add this association to the list of pending
+            // associations to be added when the package is installed in the future.
+            boolean isPackageInstalled = installedApps.stream()
+                    .anyMatch(app -> packageName.equals(app.packageName));
+            if (isPackageInstalled) {
+                mAssociationRequestsProcessor.maybeGrantRoleAndStoreAssociation(newAssociation,
+                        null, null);
+            } else {
+                // TODO(b/314992577): Check if package is installed before granting
+            }
+
+            // Re-map restored system data transfer requests to newly created associations
+            for (SystemDataTransferRequest restoredRequest : restoredRequests) {
+                SystemDataTransferRequest newRequest = restoredRequest.copyWithNewId(newId);
+                newRequest.setUserId(userId);
+                mSystemDataTransferRequestStore.writeRequest(userId, newRequest);
+            }
+        }
+
+        // Persist restored state.
+        mService.persistStateForUser(userId);
+    }
+
+    /**
+     * Detects and handles collision between restored association and local association. Returns
+     * true if there has been a collision and false otherwise.
+     */
+    private boolean handleCollision(@UserIdInt int userId,
+            AssociationInfo restored,
+            List<SystemDataTransferRequest> restoredRequests) {
+        List<AssociationInfo> localAssociations = mAssociationStore.getAssociationsForPackage(
+                restored.getUserId(), restored.getPackageName());
+        Predicate<AssociationInfo> isSameDevice = associationInfo -> {
+            boolean matchesMacAddress = Objects.equals(
+                    associationInfo.getDeviceMacAddress(),
+                    restored.getDeviceMacAddress());
+            boolean matchesTag = !Flags.associationTag()
+                    || Objects.equals(associationInfo.getTag(), restored.getTag());
+            return matchesMacAddress && matchesTag;
+        };
+        AssociationInfo local = CollectionUtils.find(localAssociations, isSameDevice);
+
+        // No collision detected
+        if (local == null) {
+            return false;
+        }
+
+        Log.d(TAG, "Conflict detected with association id=" + local.getId()
+                + " while restoring CDM backup. Keeping local association.");
+
+        List<SystemDataTransferRequest> localRequests = mSystemDataTransferRequestStore
+                .readRequestsByAssociationId(userId, local.getId());
+
+        // If local association doesn't have any existing system data transfer request of same type
+        // attached, then restore corresponding request onto the local association. Otherwise, keep
+        // the locally stored request.
+        for (SystemDataTransferRequest restoredRequest : restoredRequests) {
+            boolean requestTypeExists = CollectionUtils.any(localRequests, request ->
+                    request.getDataType() == restoredRequest.getDataType());
+
+            // This type of request consent already exists for the association.
+            if (requestTypeExists) {
+                continue;
+            }
+
+            Log.d(TAG, "Restoring " + restoredRequest.getClass().getSimpleName()
+                    + " to an existing association id=" + local.getId() + ".");
+
+            SystemDataTransferRequest newRequest =
+                    restoredRequest.copyWithNewId(local.getId());
+            newRequest.setUserId(userId);
+            mSystemDataTransferRequestStore.writeRequest(userId, newRequest);
+        }
+
+        return true;
+    }
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 487b66c..858887a 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -164,6 +164,7 @@
     private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
     private AssociationRequestsProcessor mAssociationRequestsProcessor;
     private SystemDataTransferProcessor mSystemDataTransferProcessor;
+    private BackupRestoreProcessor mBackupRestoreProcessor;
     private CompanionDevicePresenceMonitor mDevicePresenceMonitor;
     private CompanionApplicationController mCompanionAppController;
     private CompanionTransportManager mTransportManager;
@@ -256,6 +257,9 @@
         mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
                 mPackageManagerInternal, mAssociationStore,
                 mSystemDataTransferRequestStore, mTransportManager);
+        mBackupRestoreProcessor = new BackupRestoreProcessor(
+                /* cdmService */ this, mAssociationStore, mPersistentStore,
+                mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
         // TODO(b/279663946): move context sync to a dedicated system service
         mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager);
 
@@ -501,7 +505,7 @@
         updateAtm(userId, updatedAssociations);
     }
 
-    private void persistStateForUser(@UserIdInt int userId) {
+    void persistStateForUser(@UserIdInt int userId) {
         // We want to store both active associations and the revoked (removed) association that we
         // are keeping around for the final clean-up (delayed role holder removal).
         final List<AssociationInfo> allAssociations;
@@ -577,6 +581,11 @@
         mCompanionAppController.onPackagesChanged(userId);
     }
 
+    private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) {
+        if (DEBUG) Log.i(TAG, "onPackageAddedInternal() u" + userId + "/" + packageName);
+        // TODO(b/314992577): Retroactively grant roles for restored associations
+    }
+
     // Revoke associations if the selfManaged companion device does not connect for 3 months.
     void removeInactiveSelfManagedAssociations() {
         final long currentTime = System.currentTimeMillis();
@@ -1052,13 +1061,14 @@
 
         @Override
         public byte[] getBackupPayload(int userId) {
-            // TODO(b/286124853): back up CDM data
-            return new byte[0];
+            Log.i(TAG, "getBackupPayload() userId=" + userId);
+            return mBackupRestoreProcessor.getBackupPayload(userId);
         }
 
         @Override
         public void applyRestoredPayload(byte[] payload, int userId) {
-            // TODO(b/286124853): restore CDM data
+            Log.i(TAG, "applyRestoredPayload() userId=" + userId);
+            mBackupRestoreProcessor.applyRestoredPayload(payload, userId);
         }
 
         @Override
@@ -1067,7 +1077,8 @@
                 @NonNull String[] args) {
             return new CompanionDeviceShellCommand(CompanionDeviceManagerService.this,
                     mAssociationStore, mDevicePresenceMonitor, mTransportManager,
-                    mSystemDataTransferProcessor, mAssociationRequestsProcessor)
+                    mSystemDataTransferProcessor, mAssociationRequestsProcessor,
+                    mBackupRestoreProcessor)
                     .exec(this, in.getFileDescriptor(), out.getFileDescriptor(),
                             err.getFileDescriptor(), args);
         }
@@ -1499,6 +1510,11 @@
         public void onPackageModified(String packageName) {
             onPackageModifiedInternal(getChangingUserId(), packageName);
         }
+
+        @Override
+        public void onPackageAdded(String packageName, int uid) {
+            onPackageAddedInternal(getChangingUserId(), packageName);
+        }
     };
 
     static int getFirstAssociationIdForUser(@UserIdInt int userId) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 9fdf5c2..53c0184c 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -18,6 +18,8 @@
 
 import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
 
+import static com.android.server.companion.PermissionsUtils.sanitizeWithCallerChecks;
+
 import android.companion.AssociationInfo;
 import android.companion.ContextSyncMessage;
 import android.companion.Flags;
@@ -26,6 +28,7 @@
 import android.net.MacAddress;
 import android.os.Binder;
 import android.os.ShellCommand;
+import android.util.Base64;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
@@ -47,19 +50,22 @@
 
     private final SystemDataTransferProcessor mSystemDataTransferProcessor;
     private final AssociationRequestsProcessor mAssociationRequestsProcessor;
+    private final BackupRestoreProcessor mBackupRestoreProcessor;
 
     CompanionDeviceShellCommand(CompanionDeviceManagerService service,
             AssociationStoreImpl associationStore,
             CompanionDevicePresenceMonitor devicePresenceMonitor,
             CompanionTransportManager transportManager,
             SystemDataTransferProcessor systemDataTransferProcessor,
-            AssociationRequestsProcessor associationRequestsProcessor) {
+            AssociationRequestsProcessor associationRequestsProcessor,
+            BackupRestoreProcessor backupRestoreProcessor) {
         mService = service;
         mAssociationStore = associationStore;
         mDevicePresenceMonitor = devicePresenceMonitor;
         mTransportManager = transportManager;
         mSystemDataTransferProcessor = systemDataTransferProcessor;
         mAssociationRequestsProcessor = associationRequestsProcessor;
+        mBackupRestoreProcessor = backupRestoreProcessor;
     }
 
     @Override
@@ -111,6 +117,19 @@
                 }
                 break;
 
+                case "disassociate-all": {
+                    final int userId = getNextIntArgRequired();
+                    final String packageName = getNextArgRequired();
+                    final List<AssociationInfo> userAssociations =
+                            mAssociationStore.getAssociationsForPackage(userId, packageName);
+                    for (AssociationInfo association : userAssociations) {
+                        if (sanitizeWithCallerChecks(mService.getContext(), association) != null) {
+                            mService.disassociateInternal(association.getId());
+                        }
+                    }
+                }
+                break;
+
                 case "clear-association-memory-cache":
                     mService.persistState();
                     mService.loadAssociationsFromDisk();
@@ -126,6 +145,20 @@
                     mDevicePresenceMonitor.simulateDeviceEvent(associationId, /* event */ 1);
                     break;
 
+                case "get-backup-payload": {
+                    final int userId = getNextIntArgRequired();
+                    byte[] payload = mBackupRestoreProcessor.getBackupPayload(userId);
+                    out.println(Base64.encodeToString(payload, Base64.NO_WRAP));
+                }
+                break;
+
+                case "apply-restored-payload": {
+                    final int userId = getNextIntArgRequired();
+                    byte[] payload = Base64.decode(getNextArgRequired(), Base64.NO_WRAP);
+                    mBackupRestoreProcessor.applyRestoredPayload(payload, userId);
+                }
+                break;
+
                 case "remove-inactive-associations": {
                     // This command should trigger the same "clean-up" job as performed by the
                     // InactiveAssociationsRemovalService JobService. However, since the
@@ -355,6 +388,8 @@
         pw.println("      Create a new Association.");
         pw.println("  disassociate USER_ID PACKAGE MAC_ADDRESS");
         pw.println("      Remove an existing Association.");
+        pw.println("  disassociate-all USER_ID");
+        pw.println("      Remove all Associations for a user.");
         pw.println("  clear-association-memory-cache");
         pw.println("      Clear the in-memory association cache and reload all association ");
         pw.println("      information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
@@ -378,6 +413,14 @@
         pw.println("      invoked for the same device (same ASSOCIATION_ID) no longer than");
         pw.println("      60 seconds ago.");
 
+        pw.println("  get-backup-payload USER_ID");
+        pw.println("      Generate backup payload for the given user and print its content");
+        pw.println("      encoded to a Base64 string.");
+        pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+        pw.println("  apply-restored-payload USER_ID PAYLOAD");
+        pw.println("      Apply restored backup payload for the given user.");
+        pw.println("      USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
+
         if (Flags.devicePresence()) {
             pw.println("  simulate-device-event ASSOCIATION_ID EVENT");
             pw.println("  Simulate the companion device event changes:");
diff --git a/services/companion/java/com/android/server/companion/DataStoreUtils.java b/services/companion/java/com/android/server/companion/DataStoreUtils.java
index c182529..04ce1f6 100644
--- a/services/companion/java/com/android/server/companion/DataStoreUtils.java
+++ b/services/companion/java/com/android/server/companion/DataStoreUtils.java
@@ -30,8 +30,11 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.io.IOException;
 
 /**
  * Util class for CDM data stores
@@ -88,6 +91,29 @@
         }
     }
 
+    /**
+     * Read a file and return the byte array containing the bytes of the file.
+     */
+    @NonNull
+    public static byte[] fileToByteArray(@NonNull AtomicFile file) {
+        if (!file.getBaseFile().exists()) {
+            Slog.d(TAG, "File does not exist");
+            return new byte[0];
+        }
+        try (FileInputStream in = file.openRead()) {
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            byte[] buffer = new byte[1024];
+            int read;
+            while ((read = in.read(buffer)) != -1) {
+                bytes.write(buffer, 0, read);
+            }
+            return bytes.toByteArray();
+        } catch (IOException e) {
+            Slog.e(TAG, "Error while reading requests file", e);
+        }
+        return new byte[0];
+    }
+
     private DataStoreUtils() {
     }
 }
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index b4b9379..dbaf7e8 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -28,6 +28,7 @@
 import static com.android.server.companion.CompanionDeviceManagerService.getFirstAssociationIdForUser;
 import static com.android.server.companion.CompanionDeviceManagerService.getLastAssociationIdForUser;
 import static com.android.server.companion.DataStoreUtils.createStorageFileForUser;
+import static com.android.server.companion.DataStoreUtils.fileToByteArray;
 import static com.android.server.companion.DataStoreUtils.isEndOfTag;
 import static com.android.server.companion.DataStoreUtils.isStartOfTag;
 import static com.android.server.companion.DataStoreUtils.writeToFileSafely;
@@ -55,9 +56,11 @@
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
@@ -328,34 +331,42 @@
             @NonNull String rootTag, @Nullable Collection<AssociationInfo> associationsOut,
             @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
         try (FileInputStream in = file.openRead()) {
-            final TypedXmlPullParser parser = Xml.resolvePullParser(in);
-
-            XmlUtils.beginDocument(parser, rootTag);
-            final int version = readIntAttribute(parser, XML_ATTR_PERSISTENCE_VERSION, 0);
-            switch (version) {
-                case 0:
-                    readAssociationsV0(parser, userId, associationsOut);
-                    break;
-                case 1:
-                    while (true) {
-                        parser.nextTag();
-                        if (isStartOfTag(parser, XML_TAG_ASSOCIATIONS)) {
-                            readAssociationsV1(parser, userId, associationsOut);
-                        } else if (isStartOfTag(parser, XML_TAG_PREVIOUSLY_USED_IDS)) {
-                            readPreviouslyUsedIdsV1(parser, previouslyUsedIdsPerPackageOut);
-                        } else if (isEndOfTag(parser, rootTag)) {
-                            break;
-                        }
-                    }
-                    break;
-            }
-            return version;
+            return readStateFromInputStream(userId, in, rootTag, associationsOut,
+                    previouslyUsedIdsPerPackageOut);
         } catch (XmlPullParserException | IOException e) {
             Slog.e(TAG, "Error while reading associations file", e);
             return -1;
         }
     }
 
+    private int readStateFromInputStream(@UserIdInt int userId, @NonNull InputStream in,
+            @NonNull String rootTag, @Nullable Collection<AssociationInfo> associationsOut,
+            @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut)
+            throws XmlPullParserException, IOException {
+        final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+
+        XmlUtils.beginDocument(parser, rootTag);
+        final int version = readIntAttribute(parser, XML_ATTR_PERSISTENCE_VERSION, 0);
+        switch (version) {
+            case 0:
+                readAssociationsV0(parser, userId, associationsOut);
+                break;
+            case 1:
+                while (true) {
+                    parser.nextTag();
+                    if (isStartOfTag(parser, XML_TAG_ASSOCIATIONS)) {
+                        readAssociationsV1(parser, userId, associationsOut);
+                    } else if (isStartOfTag(parser, XML_TAG_PREVIOUSLY_USED_IDS)) {
+                        readPreviouslyUsedIdsV1(parser, previouslyUsedIdsPerPackageOut);
+                    } else if (isEndOfTag(parser, rootTag)) {
+                        break;
+                    }
+                }
+                break;
+        }
+        return version;
+    }
+
     private void persistStateToFileLocked(@NonNull AtomicFile file,
             @Nullable Collection<AssociationInfo> associations,
             @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
@@ -391,6 +402,26 @@
                 u -> createStorageFileForUser(userId, FILE_NAME));
     }
 
+    byte[] getBackupPayload(@UserIdInt int userId) {
+        Slog.i(TAG, "Fetching stored state data for user " + userId + " from disk");
+        final AtomicFile file = getStorageFileForUser(userId);
+
+        synchronized (file) {
+            return fileToByteArray(file);
+        }
+    }
+
+    void readStateFromPayload(byte[] payload, @UserIdInt int userId,
+                              @NonNull Set<AssociationInfo> associationsOut,
+                              @NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
+        try (ByteArrayInputStream in = new ByteArrayInputStream(payload)) {
+            readStateFromInputStream(userId, in, XML_TAG_STATE, associationsOut,
+                    previouslyUsedIdsPerPackageOut);
+        } catch (XmlPullParserException | IOException e) {
+            Slog.e(TAG, "Error while reading associations file", e);
+        }
+    }
+
     private static @NonNull File getBaseLegacyStorageFileForUser(@UserIdInt int userId) {
         return new File(Environment.getUserSystemDirectory(userId), FILE_NAME_LEGACY);
     }
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
index 9f489e8..8fe0454 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
@@ -23,6 +23,7 @@
 import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
 import static com.android.internal.util.XmlUtils.writeIntAttribute;
 import static com.android.server.companion.DataStoreUtils.createStorageFileForUser;
+import static com.android.server.companion.DataStoreUtils.fileToByteArray;
 import static com.android.server.companion.DataStoreUtils.isEndOfTag;
 import static com.android.server.companion.DataStoreUtils.isStartOfTag;
 import static com.android.server.companion.DataStoreUtils.writeToFileSafely;
@@ -44,6 +45,7 @@
 
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.ByteArrayInputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -152,6 +154,33 @@
         mExecutor.execute(() -> writeRequestsToStore(userId, cachedRequests));
     }
 
+    /**
+     * Return the byte contents of the XML file storing current system data transfer requests.
+     */
+    public byte[] getBackupPayload(@UserIdInt int userId) {
+        final AtomicFile file = getStorageFileForUser(userId);
+
+        synchronized (file) {
+            return fileToByteArray(file);
+        }
+    }
+
+    /**
+     * Parse the byte array containing XML information of system data transfer requests into
+     * an array list of requests.
+     */
+    public List<SystemDataTransferRequest> readRequestsFromPayload(byte[] payload) {
+        try (ByteArrayInputStream in = new ByteArrayInputStream(payload)) {
+            final TypedXmlPullParser parser = Xml.resolvePullParser(in);
+            XmlUtils.beginDocument(parser, XML_TAG_REQUESTS);
+
+            return readRequestsFromXml(parser);
+        } catch (XmlPullParserException | IOException e) {
+            Slog.e(LOG_TAG, "Error while reading requests file", e);
+            return new ArrayList<>();
+        }
+    }
+
     @GuardedBy("mLock")
     private ArrayList<SystemDataTransferRequest> readRequestsFromCache(@UserIdInt int userId) {
         ArrayList<SystemDataTransferRequest> cachedRequests = mCachedPerUser.get(userId);
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 39b8643..19a9239 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3236,7 +3236,7 @@
         super.createUserStorageKeys_enforcePermission();
 
         try {
-            mVold.createUserStorageKeys(userId, serialNumber, ephemeral);
+            mVold.createUserStorageKeys(userId, ephemeral);
             // Since the user's CE key was just created, the user's CE storage is now unlocked.
             synchronized (mLock) {
                 mCeUnlockedUsers.append(userId);
@@ -3281,7 +3281,7 @@
         super.unlockCeStorage_enforcePermission();
 
         if (StorageManager.isFileEncrypted()) {
-            mVold.unlockCeStorage(userId, serialNumber, HexDump.toHexString(secret));
+            mVold.unlockCeStorage(userId, HexDump.toHexString(secret));
         }
         synchronized (mLock) {
             mCeUnlockedUsers.append(userId);
@@ -3368,7 +3368,7 @@
     private void prepareUserStorageInternal(String volumeUuid, int userId, int serialNumber,
             int flags) throws Exception {
         try {
-            mVold.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
+            mVold.prepareUserStorage(volumeUuid, userId, flags);
             // After preparing user storage, we should check if we should mount data mirror again,
             // and we do it for user 0 only as we only need to do once for all users.
             if (volumeUuid != null) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 7ac4dd3..a1b6f29 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7879,7 +7879,6 @@
         DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
         DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_BLE_SET);
         DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET);
-        DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_HDMI);
     }
 
     /** only public for mocking/spying, do not call outside of AudioService */
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index cbcd8f5..4d5bce5 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -29,6 +29,7 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.server.utils.EventLogger;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -345,7 +346,8 @@
             }
             if (apc.getPlayerProxy() != null) {
                 applyVolumeShaperInternal(apc, piid, volShaper,
-                        skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
+                        skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED, skipRamp,
+                        PlaybackActivityMonitor.EVENT_TYPE_FADE_OUT);
                 mFadedPlayers.put(piid, volShaper);
             } else {
                 if (DEBUG) {
@@ -361,7 +363,8 @@
                 final AudioPlaybackConfiguration apc = players.get(piid);
                 if ((apc != null) && (apc.getPlayerProxy() != null)) {
                     applyVolumeShaperInternal(apc, piid, /* volShaperConfig= */ null,
-                            VolumeShaper.Operation.REVERSE);
+                            VolumeShaper.Operation.REVERSE, /* skipRamp= */ false,
+                            PlaybackActivityMonitor.EVENT_TYPE_FADE_IN);
                 } else {
                     // this piid was in the list of faded players, but wasn't found
                     if (DEBUG) {
@@ -373,6 +376,7 @@
             mFadedPlayers.clear();
         }
 
+        @GuardedBy("mLock")
         void fadeInPlayer(@NonNull AudioPlaybackConfiguration apc,
                 @Nullable VolumeShaper.Configuration config) {
             int piid = Integer.valueOf(apc.getPlayerInterfaceId());
@@ -385,10 +389,17 @@
                 return;
             }
 
+            VolumeShaper.Operation operation = VolumeShaper.Operation.REVERSE;
+            if (config != null) {
+                // replace and join the volumeshapers with (possibly) in progress fade out operation
+                // for a smoother fade in
+                operation = new VolumeShaper.Operation.Builder()
+                        .replace(mFadedPlayers.get(piid).getId(), /* join= */ true).build();
+            }
             mFadedPlayers.remove(piid);
             if (apc.getPlayerProxy() != null) {
-                applyVolumeShaperInternal(apc, piid, config,
-                        config != null ? PLAY_CREATE_IF_NEEDED : VolumeShaper.Operation.REVERSE);
+                applyVolumeShaperInternal(apc, piid, config, operation, /* skipRamp= */ false,
+                        PlaybackActivityMonitor.EVENT_TYPE_FADE_IN);
             } else {
                 if (DEBUG) {
                     Slog.v(TAG, "Error fading in player piid:" + piid
@@ -397,6 +408,7 @@
             }
         }
 
+        @GuardedBy("mLock")
         void clear() {
             if (mFadedPlayers.size() > 0) {
                 if (DEBUG) {
@@ -413,21 +425,40 @@
         }
 
         private void applyVolumeShaperInternal(AudioPlaybackConfiguration apc, int piid,
-                VolumeShaper.Configuration volShaperConfig, VolumeShaper.Operation operation) {
+                VolumeShaper.Configuration volShaperConfig, VolumeShaper.Operation operation,
+                boolean skipRamp, String eventType) {
             VolumeShaper.Configuration config = volShaperConfig;
             // when operation is reverse, use the fade out volume shaper config instead
             if (operation.equals(VolumeShaper.Operation.REVERSE)) {
                 config = mFadedPlayers.get(piid);
             }
             try {
-                PlaybackActivityMonitor.sEventLogger.enqueue(
-                        (new PlaybackActivityMonitor.FadeEvent(apc, config, operation))
-                                .printLog(TAG));
+                logFadeEvent(apc, piid, volShaperConfig, operation, skipRamp, eventType);
                 apc.getPlayerProxy().applyVolumeShaper(config, operation);
             } catch (Exception e) {
-                Slog.e(TAG, "Error fading player piid:" + piid + " uid:" + mUid
-                        + " operation:" + operation, e);
+                Slog.e(TAG, "Error " + eventType + " piid:" + piid + " uid:" + mUid, e);
             }
         }
+
+        private void logFadeEvent(AudioPlaybackConfiguration apc, int piid,
+                VolumeShaper.Configuration config, VolumeShaper.Operation operation,
+                boolean skipRamp, String eventType) {
+            if (eventType.equals(PlaybackActivityMonitor.EVENT_TYPE_FADE_OUT)) {
+                PlaybackActivityMonitor.sEventLogger.enqueue(
+                        (new PlaybackActivityMonitor.FadeOutEvent(apc, skipRamp, config, operation))
+                                .printLog(TAG));
+                return;
+            }
+
+            if (eventType.equals(PlaybackActivityMonitor.EVENT_TYPE_FADE_IN)) {
+                PlaybackActivityMonitor.sEventLogger.enqueue(
+                        (new PlaybackActivityMonitor.FadeInEvent(apc, skipRamp, config, operation))
+                                .printLog(TAG));
+                return;
+            }
+
+            PlaybackActivityMonitor.sEventLogger.enqueue(
+                    (new EventLogger.StringEvent(eventType + " piid:" + piid)).printLog(TAG));
+        }
     }
 }
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index e69fbbd..08da32e 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -84,6 +84,8 @@
     /*package*/ static final int VOLUME_SHAPER_SYSTEM_FADEOUT_ID = 2;
     /*package*/ static final int VOLUME_SHAPER_SYSTEM_MUTE_AWAIT_CONNECTION_ID = 3;
     /*package*/ static final int VOLUME_SHAPER_SYSTEM_STRONG_DUCK_ID = 4;
+    /*package*/ static final String EVENT_TYPE_FADE_OUT = "fading out";
+    /*package*/ static final String EVENT_TYPE_FADE_IN = "fading in";
 
     // ducking settings for a "normal duck" at -14dB
     private static final VolumeShaper.Configuration DUCK_VSHAPE =
@@ -1204,11 +1206,13 @@
                     return;
                 }
                 try {
-                    sEventLogger.enqueue((new DuckEvent(apc, skipRamp, mUseStrongDuck))
-                            .printLog(TAG));
-                    apc.getPlayerProxy().applyVolumeShaper(
-                            mUseStrongDuck ? STRONG_DUCK_VSHAPE : DUCK_VSHAPE,
-                            skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
+                    VolumeShaper.Configuration config =
+                            mUseStrongDuck ? STRONG_DUCK_VSHAPE : DUCK_VSHAPE;
+                    VolumeShaper.Operation operation =
+                            skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED;
+                    sEventLogger.enqueue((new DuckEvent(apc, skipRamp, mUseStrongDuck, config,
+                            operation)).printLog(TAG));
+                    apc.getPlayerProxy().applyVolumeShaper(config, operation);
                     mDuckedPlayers.add(piid);
                 } catch (Exception e) {
                     Log.e(TAG, "Error ducking player piid:" + piid + " uid:" + mUid, e);
@@ -1363,58 +1367,41 @@
         }
     }
 
-    static final class FadeEvent extends EventLogger.Event {
-        private final int mPlayerIId;
-        private final int mPlayerType;
-        private final int mClientUid;
-        private final int mClientPid;
-        private final AudioAttributes mPlayerAttr;
-        private final VolumeShaper.Configuration mVShaper;
-        private final VolumeShaper.Operation mVOperation;
-
-        FadeEvent(AudioPlaybackConfiguration apc, VolumeShaper.Configuration vShaper,
-                VolumeShaper.Operation vOperation) {
-            mPlayerIId = apc.getPlayerInterfaceId();
-            mClientUid = apc.getClientUid();
-            mClientPid = apc.getClientPid();
-            mPlayerAttr = apc.getAudioAttributes();
-            mPlayerType = apc.getPlayerType();
-            mVShaper = vShaper;
-            mVOperation = vOperation;
-        }
-
-        @Override
-        public String eventToString() {
-            return "Fade Event:" + " player piid:" + mPlayerIId
-                    + " uid/pid:" + mClientUid + "/" + mClientPid
-                    + " player type:"
-                    + AudioPlaybackConfiguration.toLogFriendlyPlayerType(mPlayerType)
-                    + " attr:" + mPlayerAttr
-                    + " volume shaper:" + mVShaper
-                    + " volume operation:" + mVOperation;
-        }
-    }
-
     private abstract static class VolumeShaperEvent extends EventLogger.Event {
         private final int mPlayerIId;
         private final boolean mSkipRamp;
         private final int mClientUid;
         private final int mClientPid;
+        private final int mPlayerType;
+        private final AudioAttributes mPlayerAttr;
+        private final VolumeShaper.Configuration mConfig;
+        private final VolumeShaper.Operation mOperation;
 
         abstract String getVSAction();
 
-        VolumeShaperEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
+        VolumeShaperEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp,
+                VolumeShaper.Configuration config, VolumeShaper.Operation operation) {
             mPlayerIId = apc.getPlayerInterfaceId();
             mSkipRamp = skipRamp;
             mClientUid = apc.getClientUid();
             mClientPid = apc.getClientPid();
+            mPlayerAttr = apc.getAudioAttributes();
+            mPlayerType = apc.getPlayerType();
+            mConfig = config;
+            mOperation = operation;
         }
 
         @Override
         public String eventToString() {
-            return new StringBuilder(getVSAction()).append(" player piid:").append(mPlayerIId)
-                    .append(" uid/pid:").append(mClientUid).append("/").append(mClientPid)
-                    .append(" skip ramp:").append(mSkipRamp).toString();
+            return getVSAction()
+                    + " player piid:" + mPlayerIId
+                    + " uid/pid:" + mClientUid + "/" + mClientPid
+                    + " skip ramp:" + mSkipRamp
+                    + " player type:"
+                    + AudioPlaybackConfiguration.toLogFriendlyPlayerType(mPlayerType)
+                    + " attr:" + mPlayerAttr
+                    + " config:" + mConfig
+                    + " operation:" + mOperation;
         }
     }
 
@@ -1426,9 +1413,10 @@
             return mUseStrongDuck ? "ducking (strong)" : "ducking";
         }
 
-        DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp, boolean useStrongDuck)
+        DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp, boolean useStrongDuck,
+                VolumeShaper.Configuration config, VolumeShaper.Operation operation)
         {
-            super(apc, skipRamp);
+            super(apc, skipRamp, config, operation);
             mUseStrongDuck = useStrongDuck;
         }
     }
@@ -1436,11 +1424,24 @@
     static final class FadeOutEvent extends VolumeShaperEvent {
         @Override
         String getVSAction() {
-            return "fading out";
+            return EVENT_TYPE_FADE_OUT;
         }
 
-        FadeOutEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
-            super(apc, skipRamp);
+        FadeOutEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp,
+                VolumeShaper.Configuration config, VolumeShaper.Operation operation) {
+            super(apc, skipRamp, config, operation);
+        }
+    }
+
+    static final class FadeInEvent extends VolumeShaperEvent {
+        @Override
+        String getVSAction() {
+            return EVENT_TYPE_FADE_IN;
+        }
+
+        FadeInEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp,
+                VolumeShaper.Configuration config, VolumeShaper.Operation operation) {
+            super(apc, skipRamp, config, operation);
         }
     }
 
diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
index 1ac3a12..e54f30f 100644
--- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
+++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java
@@ -27,6 +27,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.config.layout.Layouts;
 import com.android.server.display.config.layout.XmlParser;
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.layout.DisplayIdProducer;
 import com.android.server.display.layout.Layout;
 
@@ -63,24 +64,40 @@
     private static final String CONFIG_FILE_PATH =
             "etc/displayconfig/display_layout_configuration.xml";
 
+    private static final String DATA_CONFIG_FILE_PATH =
+            "system/displayconfig/display_layout_configuration.xml";
+
     private final SparseArray<Layout> mLayoutMap = new SparseArray<>();
     private final DisplayIdProducer mIdProducer;
+    private final boolean mIsPortInDisplayLayoutEnabled;
 
-    DeviceStateToLayoutMap(DisplayIdProducer idProducer) {
-        this(idProducer, Environment.buildPath(
-                Environment.getVendorDirectory(), CONFIG_FILE_PATH));
+    DeviceStateToLayoutMap(DisplayIdProducer idProducer, DisplayManagerFlags flags) {
+        this(idProducer, flags, getConfigFile());
     }
 
-    DeviceStateToLayoutMap(DisplayIdProducer idProducer, File configFile) {
+    DeviceStateToLayoutMap(DisplayIdProducer idProducer, DisplayManagerFlags flags,
+            File configFile) {
+        mIsPortInDisplayLayoutEnabled = flags.isPortInDisplayLayoutEnabled();
         mIdProducer = idProducer;
         loadLayoutsFromConfig(configFile);
         createLayout(STATE_DEFAULT);
     }
 
+    static private File getConfigFile() {
+        final File configFileFromDataDir = Environment.buildPath(Environment.getDataDirectory(),
+                DATA_CONFIG_FILE_PATH);
+        if (configFileFromDataDir.exists()) {
+            return configFileFromDataDir;
+        } else {
+            return Environment.buildPath(Environment.getVendorDirectory(), CONFIG_FILE_PATH);
+        }
+    }
+
     public void dumpLocked(IndentingPrintWriter ipw) {
         ipw.println("DeviceStateToLayoutMap:");
         ipw.increaseIndent();
 
+        ipw.println("mIsPortInDisplayLayoutEnabled=" + mIsPortInDisplayLayoutEnabled);
         ipw.println("Registered Layouts:");
         for (int i = 0; i < mLayoutMap.size(); i++) {
             ipw.println("state(" + mLayoutMap.keyAt(i) + "): " + mLayoutMap.valueAt(i));
@@ -120,13 +137,15 @@
                 final Layout layout = createLayout(state);
                 for (com.android.server.display.config.layout.Display d: l.getDisplay()) {
                     assert layout != null;
+                    final DisplayAddress address = getDisplayAddressForLayoutDisplay(d);
+
                     int position = getPosition(d.getPosition());
                     BigInteger leadDisplayPhysicalId = d.getLeadDisplayAddress();
                     DisplayAddress leadDisplayAddress = leadDisplayPhysicalId == null ? null
                             : DisplayAddress.fromPhysicalDisplayId(
                                     leadDisplayPhysicalId.longValue());
                     layout.createDisplayLocked(
-                            DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()),
+                            address,
                             d.isDefaultDisplay(),
                             d.isEnabled(),
                             d.getDisplayGroup(),
@@ -146,6 +165,20 @@
         }
     }
 
+    private DisplayAddress getDisplayAddressForLayoutDisplay(
+            @NonNull com.android.server.display.config.layout.Display display) {
+        BigInteger xmlAddress = display.getAddress_optional();
+        if (xmlAddress != null) {
+            return DisplayAddress.fromPhysicalDisplayId(xmlAddress.longValue());
+        }
+        if (!mIsPortInDisplayLayoutEnabled || display.getPort_optional() == null) {
+            throw new IllegalArgumentException(
+                  "Must specify a display identifier in display layout configuration: " + display);
+        }
+        return DisplayAddress.fromPortAndModel((int) display.getPort_optional().longValue(),
+                /* model= */ null);
+    }
+
     private int getPosition(@NonNull String position) {
         int positionInt = POSITION_UNKNOWN;
         if (FRONT_STRING.equals(position)) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
index 67e612d..6164154 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -129,7 +129,9 @@
     public DisplayDevice getByAddressLocked(@NonNull DisplayAddress address) {
         for (int i = mDisplayDevices.size() - 1; i >= 0; i--) {
             final DisplayDevice device = mDisplayDevices.get(i);
-            if (address.equals(device.getDisplayDeviceInfoLocked().address)) {
+            final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+            if (address.equals(info.address)
+                    || DisplayAddress.Physical.isPortMatch(address, info.address)) {
                 return device;
             }
         }
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 115111a..2e8de31 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -205,7 +205,7 @@
             @NonNull Handler handler, DisplayManagerFlags flags) {
         this(context, foldSettingProvider, repo, listener, syncRoot, handler,
                 new DeviceStateToLayoutMap((isDefault) -> isDefault ? DEFAULT_DISPLAY
-                        : sNextNonDefaultDisplayId++), flags);
+                        : sNextNonDefaultDisplayId++, flags), flags);
     }
 
     LogicalDisplayMapper(@NonNull Context context, FoldSettingProvider foldSettingProvider,
@@ -1094,8 +1094,8 @@
             final DisplayAddress address = displayLayout.getAddress();
             final DisplayDevice device = mDisplayDeviceRepo.getByAddressLocked(address);
             if (device == null) {
-                Slog.w(TAG, "The display device (" + address + "), is not available"
-                        + " for the display state " + mDeviceState);
+                Slog.w(TAG, "applyLayoutLocked: The display device (" + address + "), is not "
+                        + "available for the display state " + mDeviceState);
                 continue;
             }
 
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index aa7f07d..be48eb4 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -37,6 +37,9 @@
     // 'adb shell setprop persist.log.tag.DisplayManagerFlags DEBUG && adb reboot'
     private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
 
+    private final FlagState mPortInDisplayLayoutFlagState = new FlagState(
+            Flags.FLAG_ENABLE_PORT_IN_DISPLAY_LAYOUT,
+            Flags::enablePortInDisplayLayout);
 
     private final FlagState mConnectedDisplayManagementFlagState = new FlagState(
             Flags.FLAG_ENABLE_CONNECTED_DISPLAY_MANAGEMENT,
@@ -109,6 +112,18 @@
             Flags.FLAG_FAST_HDR_TRANSITIONS,
             Flags::fastHdrTransitions);
 
+    private final FlagState mRefreshRateVotingTelemetry = new FlagState(
+            Flags.FLAG_REFRESH_RATE_VOTING_TELEMETRY,
+            Flags::refreshRateVotingTelemetry
+    );
+
+    /**
+     * @return {@code true} if 'port' is allowed in display layout configuration file.
+     */
+    public boolean isPortInDisplayLayoutEnabled() {
+        return mPortInDisplayLayoutFlagState.isEnabled();
+    }
+
     /** Returns whether connected display management is enabled or not. */
     public boolean isConnectedDisplayManagementEnabled() {
         return mConnectedDisplayManagementFlagState.isEnabled();
@@ -220,6 +235,10 @@
         return mFastHdrTransitions.isEnabled();
     }
 
+    public boolean isRefreshRateVotingTelemetryEnabled() {
+        return mRefreshRateVotingTelemetry.isEnabled();
+    }
+
     /**
      * dumps all flagstates
      * @param pw printWriter
@@ -242,6 +261,7 @@
         pw.println(" " + mBrightnessWearBedtimeModeClamperFlagState);
         pw.println(" " + mAutoBrightnessModesFlagState);
         pw.println(" " + mFastHdrTransitions);
+        pw.println(" " + mRefreshRateVotingTelemetry);
     }
 
     private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 04ecbb9..c9569cb 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -3,6 +3,14 @@
 # Important: Flags must be accessed through DisplayManagerFlags.
 
 flag {
+    name: "enable_port_in_display_layout"
+    namespace: "display_manager"
+    description: "Allows refering to displays by port in display layout"
+    bug: "303058435"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "enable_connected_display_management"
     namespace: "display_manager"
     description: "Feature flag for Connected Display management"
@@ -161,3 +169,10 @@
     is_fixed_read_only: true
 }
 
+flag {
+    name: "refresh_rate_voting_telemetry"
+    namespace: "display_manager"
+    description: "Feature flag for enabling telemetry for refresh rate voting in DisplayManager"
+    bug: "310029108"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java
index 40cb3303..8a362f7 100644
--- a/services/core/java/com/android/server/display/layout/Layout.java
+++ b/services/core/java/com/android/server/display/layout/Layout.java
@@ -200,13 +200,7 @@
      * @return True if the specified address is used in this layout.
      */
     public boolean contains(@NonNull DisplayAddress address) {
-        final int size = mDisplays.size();
-        for (int i = 0; i < size; i++) {
-            if (address.equals(mDisplays.get(i).getAddress())) {
-                return true;
-            }
-        }
-        return false;
+        return getByAddress(address) != null;
     }
 
     /**
@@ -237,6 +231,9 @@
             if (address.equals(display.getAddress())) {
                 return display;
             }
+            if (DisplayAddress.Physical.isPortMatch(address, display.getAddress())) {
+                return display;
+            }
         }
         return null;
     }
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 6e503cb..50e9533 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -154,6 +154,9 @@
 
     private final VotesStorage mVotesStorage;
 
+    @Nullable
+    private final VotesStatsReporter mVotesStatsReporter;
+
     /**
      * The allowed refresh rate switching type. This is used by SurfaceFlinger.
      */
@@ -204,6 +207,8 @@
         mContext = context;
         mHandler = new DisplayModeDirectorHandler(handler.getLooper());
         mInjector = injector;
+        mVotesStatsReporter = injector.getVotesStatsReporter(
+                displayManagerFlags.isRefreshRateVotingTelemetryEnabled());
         mSupportedModesByDisplay = new SparseArray<>();
         mDefaultModeByDisplay = new SparseArray<>();
         mAppRequestObserver = new AppRequestObserver();
@@ -214,7 +219,8 @@
         mBrightnessObserver = new BrightnessObserver(context, handler, injector);
         mDefaultDisplayDeviceConfig = null;
         mUdfpsObserver = new UdfpsObserver();
-        mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked);
+        mVotesStorage = new VotesStorage(this::notifyDesiredDisplayModeSpecsChangedLocked,
+                mVotesStatsReporter);
         mDisplayObserver = new DisplayObserver(context, handler, mVotesStorage);
         mSensorObserver = new SensorObserver(context, mVotesStorage, injector);
         mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, mVotesStorage);
@@ -341,6 +347,11 @@
             appRequestSummary.limitRefreshRanges(primarySummary);
 
             Display.Mode baseMode = primarySummary.selectBaseMode(availableModes, defaultMode);
+            if (mVotesStatsReporter != null) {
+                mVotesStatsReporter.reportVotesActivated(displayId, lowestConsideredPriority,
+                        baseMode, votes);
+            }
+
             if (baseMode == null) {
                 Slog.w(TAG, "Can't find a set of allowed modes which satisfies the votes. Falling"
                         + " back to the default mode. Display = " + displayId + ", votes = " + votes
@@ -970,9 +981,14 @@
 
                 if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) {
                     // The flag had been turned off, we need to restore the original value
-                    Settings.System.putFloatForUser(cr,
-                            Settings.System.MIN_REFRESH_RATE, minRefreshRate, cr.getUserId());
+                    Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
+                            highestRefreshRate, cr.getUserId());
                 }
+            } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+                    && Math.round(minRefreshRate) == Math.round(highestRefreshRate)) {
+                // The flag has been turned on, we need to upgrade the setting
+                Settings.System.putFloatForUser(cr, Settings.System.MIN_REFRESH_RATE,
+                        Float.POSITIVE_INFINITY, cr.getUserId());
             }
 
             float peakRefreshRate = Settings.System.getFloatForUser(cr,
@@ -983,9 +999,14 @@
 
                 if (!mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled) {
                     // The flag had been turned off, we need to restore the original value
-                    Settings.System.putFloatForUser(cr,
-                            Settings.System.PEAK_REFRESH_RATE, peakRefreshRate, cr.getUserId());
+                    Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
+                            highestRefreshRate, cr.getUserId());
                 }
+            } else if (mIsBackUpSmoothDisplayAndForcePeakRefreshRateEnabled
+                    && Math.round(peakRefreshRate) == Math.round(highestRefreshRate)) {
+                // The flag has been turned on, we need to upgrade the setting
+                Settings.System.putFloatForUser(cr, Settings.System.PEAK_REFRESH_RATE,
+                        Float.POSITIVE_INFINITY, cr.getUserId());
             }
 
             updateRefreshRateSettingLocked(minRefreshRate, peakRefreshRate, mDefaultRefreshRate);
@@ -2811,6 +2832,9 @@
         StatusBarManagerInternal getStatusBarManagerInternal();
 
         SensorManagerInternal getSensorManagerInternal();
+
+        @Nullable
+        VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled);
     }
 
     @VisibleForTesting
@@ -2943,6 +2967,13 @@
             return LocalServices.getService(SensorManagerInternal.class);
         }
 
+        @Override
+        public VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled) {
+            // if frame rate override supported, renderRates will be ignored in mode selection
+            return new VotesStatsReporter(supportsFrameRateOverride(),
+                    refreshRateVotingTelemetryEnabled);
+        }
+
         private DisplayManager getDisplayManager() {
             if (mDisplayManager == null) {
                 mDisplayManager = mContext.getSystemService(DisplayManager.class);
diff --git a/services/core/java/com/android/server/display/mode/VotesStatsReporter.java b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
new file mode 100644
index 0000000..a30c4d2
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/VotesStatsReporter.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Trace;
+import android.util.SparseArray;
+import android.view.Display;
+
+/**
+ * The VotesStatsReporter is responsible for collecting and sending Vote related statistics 
+ */
+class VotesStatsReporter {
+    private static final String TAG = "VotesStatsReporter";
+    private static final int REFRESH_RATE_NOT_LIMITED = 1000;
+    private final boolean mIgnoredRenderRate;
+    private final boolean mFrameworkStatsLogReportingEnabled;
+
+    public VotesStatsReporter(boolean ignoreRenderRate, boolean refreshRateVotingTelemetryEnabled) {
+        mIgnoredRenderRate = ignoreRenderRate;
+        mFrameworkStatsLogReportingEnabled = refreshRateVotingTelemetryEnabled;
+    }
+
+    void reportVoteAdded(int displayId, int priority,  @NonNull Vote vote) {
+        int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
+        Trace.traceCounter(Trace.TRACE_TAG_POWER,
+                TAG + "." + displayId + ":" + Vote.priorityToString(priority), maxRefreshRate);
+        // if ( mFrameworkStatsLogReportingEnabled) {
+        // FrameworkStatsLog.write(VOTE_CHANGED, displayID, priority, ADDED, maxRefreshRate, -1);
+        // }
+    }
+
+    void reportVoteRemoved(int displayId, int priority) {
+        Trace.traceCounter(Trace.TRACE_TAG_POWER,
+                TAG + "." + displayId + ":" + Vote.priorityToString(priority), -1);
+        // if ( mFrameworkStatsLogReportingEnabled) {
+        // FrameworkStatsLog.write(VOTE_CHANGED, displayID, priority, REMOVED, -1, -1);
+        // }
+    }
+
+    void reportVotesActivated(int displayId, int minPriority, @Nullable Display.Mode baseMode,
+            SparseArray<Vote> votes) {
+//        if (!mFrameworkStatsLogReportingEnabled) {
+//            return;
+//        }
+//        int selectedRefreshRate = baseMode != null ? (int) baseMode.getRefreshRate() : -1;
+//        for (int priority = minPriority; priority <= Vote.MAX_PRIORITY; priority ++) {
+//            Vote vote = votes.get(priority);
+//            if (vote != null) {
+//                int maxRefreshRate = getMaxRefreshRate(vote, mIgnoredRenderRate);
+//                FrameworkStatsLog.write(VOTE_CHANGED, displayId, priority,
+//                        ACTIVE, maxRefreshRate, selectedRefreshRate);
+//            }
+//        }
+    }
+
+    private static int getMaxRefreshRate(@NonNull Vote vote, boolean ignoreRenderRate) {
+        int maxRefreshRate = REFRESH_RATE_NOT_LIMITED;
+        if (vote instanceof RefreshRateVote.PhysicalVote physicalVote) {
+            maxRefreshRate = (int) physicalVote.mMaxRefreshRate;
+        } else if (!ignoreRenderRate && (vote instanceof RefreshRateVote.RenderVote renderVote)) {
+            maxRefreshRate = (int)  renderVote.mMaxRefreshRate;
+        } else if (vote instanceof SupportedModesVote supportedModesVote) {
+            // SupportedModesVote limits mode by specific refreshRates, so highest rr is allowed
+            maxRefreshRate = 0;
+            for (SupportedModesVote.SupportedMode mode : supportedModesVote.mSupportedModes) {
+                maxRefreshRate = Math.max(maxRefreshRate, (int) mode.mPeakRefreshRate);
+            }
+        } else if (vote instanceof CombinedVote combinedVote) {
+            for (Vote subVote: combinedVote.mVotes) {
+                // CombinedVote should not have CombinedVote in mVotes, so recursion depth will be 1
+                maxRefreshRate = Math.min(maxRefreshRate,
+                        getMaxRefreshRate(subVote, ignoreRenderRate));
+            }
+        }
+        return maxRefreshRate;
+    }
+}
diff --git a/services/core/java/com/android/server/display/mode/VotesStorage.java b/services/core/java/com/android/server/display/mode/VotesStorage.java
index 95fb8fc..7a1f7e9 100644
--- a/services/core/java/com/android/server/display/mode/VotesStorage.java
+++ b/services/core/java/com/android/server/display/mode/VotesStorage.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.os.Trace;
 import android.util.Slog;
 import android.util.SparseArray;
 
@@ -38,6 +37,9 @@
 
     private final Listener mListener;
 
+    @Nullable
+    private final VotesStatsReporter mVotesStatsReporter;
+
     private final Object mStorageLock = new Object();
     // A map from the display ID to the collection of votes and their priority. The latter takes
     // the form of another map from the priority to the vote itself so that each priority is
@@ -45,8 +47,9 @@
     @GuardedBy("mStorageLock")
     private final SparseArray<SparseArray<Vote>> mVotesByDisplay = new SparseArray<>();
 
-    VotesStorage(@NonNull Listener listener) {
+    VotesStorage(@NonNull Listener listener, @Nullable VotesStatsReporter votesStatsReporter) {
         mListener = listener;
+        mVotesStatsReporter = votesStatsReporter;
     }
     /** sets logging enabled/disabled for this class */
     void setLoggingEnabled(boolean loggingEnabled) {
@@ -110,17 +113,26 @@
                 changed = true;
             }
         }
-        Trace.traceCounter(Trace.TRACE_TAG_POWER,
-                TAG + "." + displayId + ":" + Vote.priorityToString(priority),
-                getMaxPhysicalRefreshRate(vote));
         if (mLoggingEnabled) {
             Slog.i(TAG, "Updated votes for display=" + displayId + " votes=" + votes);
         }
         if (changed) {
+            reportVoteStats(displayId, priority, vote);
             mListener.onChanged();
         }
     }
 
+    private void reportVoteStats(int displayId, int priority, @Nullable Vote vote) {
+        if (mVotesStatsReporter == null) {
+            return;
+        }
+        if (vote == null) {
+            mVotesStatsReporter.reportVoteRemoved(displayId, priority);
+        } else {
+            mVotesStatsReporter.reportVoteAdded(displayId, priority, vote);
+        }
+    }
+
     /** dump class values, for debugging */
     void dump(@NonNull PrintWriter pw) {
         SparseArray<SparseArray<Vote>> votesByDisplayLocal = new SparseArray<>();
@@ -157,21 +169,6 @@
         }
     }
 
-    private static int getMaxPhysicalRefreshRate(@Nullable Vote vote) {
-        if (vote == null) {
-            return -1;
-        } else if (vote instanceof RefreshRateVote.PhysicalVote physicalVote) {
-            return (int) physicalVote.mMaxRefreshRate;
-        } else if (vote instanceof CombinedVote combinedVote) {
-            return combinedVote.mVotes.stream()
-                    .filter(v -> v instanceof RefreshRateVote.PhysicalVote)
-                    .map(pv -> (int) (((RefreshRateVote.PhysicalVote) pv).mMaxRefreshRate))
-                    .min(Integer::compare)
-                    .orElse(1000); // for visualisation
-        }
-        return 1000; // for visualisation, otherwise e.g. -1 -> 60 will be unnoticeable
-    }
-
     interface Listener {
         void onChanged();
     }
diff --git a/services/core/java/com/android/server/inputmethod/ClientController.java b/services/core/java/com/android/server/inputmethod/ClientController.java
new file mode 100644
index 0000000..2934640
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ClientController.java
@@ -0,0 +1,162 @@
+/*
+ * 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.inputmethod;
+
+import android.annotation.NonNull;
+import android.content.pm.PackageManagerInternal;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.view.inputmethod.InputBinding;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+
+/**
+ * Store and manage {@link InputMethodManagerService} clients. This class was designed to be a
+ * singleton in {@link InputMethodManagerService} since it stores information about all clients,
+ * still the current client will be defined per display.
+ *
+ * <p>
+ * As part of the re-architecture plan (described in go/imms-rearchitecture-plan), the following
+ * fields and methods will be moved out from IMMS and placed here:
+ * <ul>
+ * <li>mCurClient (ClientState)</li>
+ * <li>mClients (ArrayMap of ClientState indexed by IBinder)</li>
+ * <li>mLastSwitchUserId</li>
+ * </ul>
+ * <p>
+ * Nested Classes (to move from IMMS):
+ * <ul>
+ * <li>ClientDeathRecipient</li>
+ * <li>ClientState<</li>
+ * </ul>
+ * <p>
+ * Methods to rewrite and/or extract from IMMS and move here:
+ * <ul>
+ * <li>addClient</li>
+ * <li>removeClient</li>
+ * <li>verifyClientAndPackageMatch</li>
+ * <li>setImeTraceEnabledForAllClients (make it reactive)</li>
+ * <li>unbindCurrentClient</li>
+ * </ul>
+ */
+// TODO(b/314150112): Update the Javadoc above, by removing the re-architecture steps, once this
+//  class is finalized
+final class ClientController {
+
+    // TODO(b/314150112): Make this field private when breaking the cycle with IMMS.
+    @GuardedBy("ImfLock.class")
+    final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>();
+
+    private final PackageManagerInternal mPackageManagerInternal;
+
+    ClientController(PackageManagerInternal packageManagerInternal) {
+        mPackageManagerInternal = packageManagerInternal;
+    }
+
+    @GuardedBy("ImfLock.class")
+    void addClient(IInputMethodClientInvoker clientInvoker,
+            IRemoteInputConnection inputConnection,
+            int selfReportedDisplayId, IBinder.DeathRecipient deathRecipient, int callerUid,
+            int callerPid) {
+        // TODO: Optimize this linear search.
+        final int numClients = mClients.size();
+        for (int i = 0; i < numClients; ++i) {
+            final ClientState state = mClients.valueAt(i);
+            if (state.mUid == callerUid && state.mPid == callerPid
+                    && state.mSelfReportedDisplayId == selfReportedDisplayId) {
+                throw new SecurityException("uid=" + callerUid + "/pid=" + callerPid
+                        + "/displayId=" + selfReportedDisplayId + " is already registered");
+            }
+        }
+        try {
+            clientInvoker.asBinder().linkToDeath(deathRecipient, 0 /* flags */);
+        } catch (RemoteException e) {
+            throw new IllegalStateException(e);
+        }
+        // We cannot fully avoid race conditions where the client UID already lost the access to
+        // the given self-reported display ID, even if the client is not maliciously reporting
+        // a fake display ID. Unconditionally returning SecurityException just because the
+        // client doesn't pass display ID verification can cause many test failures hence not an
+        // option right now.  At the same time
+        //    context.getSystemService(InputMethodManager.class)
+        // is expected to return a valid non-null instance at any time if we do not choose to
+        // have the client crash.  Thus we do not verify the display ID at all here.  Instead we
+        // later check the display ID every time the client needs to interact with the specified
+        // display.
+        mClients.put(clientInvoker.asBinder(), new ClientState(clientInvoker, inputConnection,
+                callerUid, callerPid, selfReportedDisplayId, deathRecipient));
+    }
+
+    @GuardedBy("ImfLock.class")
+    boolean verifyClientAndPackageMatch(
+            @NonNull IInputMethodClient client, @NonNull String packageName) {
+        ClientState cs = mClients.get(client.asBinder());
+        if (cs == null) {
+            throw new IllegalArgumentException("unknown client " + client.asBinder());
+        }
+        return InputMethodUtils.checkIfPackageBelongsToUid(
+                mPackageManagerInternal, cs.mUid, packageName);
+    }
+
+    static final class ClientState {
+        final IInputMethodClientInvoker mClient;
+        final IRemoteInputConnection mFallbackInputConnection;
+        final int mUid;
+        final int mPid;
+        final int mSelfReportedDisplayId;
+        final InputBinding mBinding;
+        final IBinder.DeathRecipient mClientDeathRecipient;
+
+        @GuardedBy("ImfLock.class")
+        boolean mSessionRequested;
+
+        @GuardedBy("ImfLock.class")
+        boolean mSessionRequestedForAccessibility;
+
+        @GuardedBy("ImfLock.class")
+        InputMethodManagerService.SessionState mCurSession;
+
+        @GuardedBy("ImfLock.class")
+        SparseArray<InputMethodManagerService.AccessibilitySessionState> mAccessibilitySessions =
+                new SparseArray<>();
+
+        @Override
+        public String toString() {
+            return "ClientState{" + Integer.toHexString(
+                    System.identityHashCode(this)) + " mUid=" + mUid
+                    + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
+        }
+
+        ClientState(IInputMethodClientInvoker client,
+                IRemoteInputConnection fallbackInputConnection,
+                int uid, int pid, int selfReportedDisplayId,
+                IBinder.DeathRecipient clientDeathRecipient) {
+            mClient = client;
+            mFallbackInputConnection = fallbackInputConnection;
+            mUid = uid;
+            mPid = pid;
+            mSelfReportedDisplayId = selfReportedDisplayId;
+            mBinding = new InputBinding(null /*conn*/, mFallbackInputConnection.asBinder(), mUid,
+                    mPid);
+            mClientDeathRecipient = clientDeathRecipient;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
index f0e4b0f5..898d5a5 100644
--- a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
+++ b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
@@ -19,6 +19,8 @@
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.util.ArrayMap;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
@@ -33,9 +35,26 @@
     @GuardedBy("ImfLock.class")
     private final ArrayList<InputMethodSubtypeHandle> mSubtypeHandles = new ArrayList<>();
 
+    @UserIdInt
+    private final int mUserId;
+
+    @AnyThread
+    @UserIdInt
+    int getUserId() {
+        return mUserId;
+    }
+
+    HardwareKeyboardShortcutController(
+            @NonNull ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
+        mUserId = userId;
+        reset(methodMap);
+    }
+
     @GuardedBy("ImfLock.class")
-    void reset(@NonNull InputMethodUtils.InputMethodSettings settings) {
+    void reset(@NonNull ArrayMap<String, InputMethodInfo> methodMap) {
         mSubtypeHandles.clear();
+        final InputMethodUtils.InputMethodSettings settings =
+                new InputMethodUtils.InputMethodSettings(methodMap, mUserId);
         for (final InputMethodInfo imi : settings.getEnabledInputMethodListLocked()) {
             if (!imi.shouldShowInInputMethodPicker()) {
                 continue;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 24bcb4e..25ec683 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server.inputmethod;
 
+import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
 import static android.os.IServiceManager.DUMP_FLAG_PROTO;
@@ -47,6 +48,7 @@
 import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 
+import static com.android.server.inputmethod.ClientController.ClientState;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
@@ -126,7 +128,6 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.Flags;
 import android.view.inputmethod.ImeTracker;
-import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceFileProto;
@@ -272,13 +273,15 @@
     @NonNull
     private final String[] mNonPreemptibleInputMethods;
 
+    // TODO(b/314150112): Move this to ClientController.
     @UserIdInt
     private int mLastSwitchUserId;
 
     final Context mContext;
     final Resources mRes;
     private final Handler mHandler;
-    private final InputMethodSettings mSettings;
+    @NonNull
+    private InputMethodSettings mSettings;
     final SettingsObserver mSettingsObserver;
     private final SparseBooleanArray mLoggedDeniedGetInputMethodWindowVisibleHeightForUid =
             new SparseBooleanArray(0);
@@ -315,12 +318,17 @@
     // All known input methods.
     final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
     private final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>();
+
     // Mapping from deviceId to the device-specific imeId for that device.
+    @GuardedBy("ImfLock.class")
     private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>();
 
-    private final InputMethodSubtypeSwitchingController mSwitchingController;
-    final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController =
-            new HardwareKeyboardShortcutController();
+    // TODO: Instantiate mSwitchingController for each user.
+    @NonNull
+    private InputMethodSubtypeSwitchingController mSwitchingController;
+    // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
+    @NonNull
+    private HardwareKeyboardShortcutController mHardwareKeyboardShortcutController;
 
     /**
      * Tracks how many times {@link #mMethodMap} was updated.
@@ -339,6 +347,9 @@
     @GuardedBy("ImfLock.class")
     private int mDisplayIdToShowIme = INVALID_DISPLAY;
 
+    @GuardedBy("ImfLock.class")
+    private int mDeviceIdToShowIme = DEVICE_ID_DEFAULT;
+
     @Nullable private StatusBarManagerInternal mStatusBarManagerInternal;
     private boolean mShowOngoingImeSwitcherForPhones;
     @GuardedBy("ImfLock.class")
@@ -383,7 +394,7 @@
     /**
      * Record session state for an accessibility service.
      */
-    private static class AccessibilitySessionState {
+    static class AccessibilitySessionState {
         final ClientState mClient;
         // Id of the accessibility service.
         final int mId;
@@ -407,58 +418,10 @@
         }
     }
 
-    private static final class ClientDeathRecipient implements IBinder.DeathRecipient {
-        private final InputMethodManagerService mImms;
-        private final IInputMethodClient mClient;
-
-        ClientDeathRecipient(InputMethodManagerService imms, IInputMethodClient client) {
-            mImms = imms;
-            mClient = client;
-        }
-
-        @Override
-        public void binderDied() {
-            mImms.removeClient(mClient);
-        }
-    }
-
-    static final class ClientState {
-        final IInputMethodClientInvoker mClient;
-        final IRemoteInputConnection mFallbackInputConnection;
-        final int mUid;
-        final int mPid;
-        final int mSelfReportedDisplayId;
-        final InputBinding mBinding;
-        final ClientDeathRecipient mClientDeathRecipient;
-
-        boolean mSessionRequested;
-        boolean mSessionRequestedForAccessibility;
-        SessionState mCurSession;
-        SparseArray<AccessibilitySessionState> mAccessibilitySessions = new SparseArray<>();
-
-        @Override
-        public String toString() {
-            return "ClientState{" + Integer.toHexString(
-                    System.identityHashCode(this)) + " mUid=" + mUid
-                    + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
-        }
-
-        ClientState(IInputMethodClientInvoker client,
-                IRemoteInputConnection fallbackInputConnection,
-                int uid, int pid, int selfReportedDisplayId,
-                ClientDeathRecipient clientDeathRecipient) {
-            mClient = client;
-            mFallbackInputConnection = fallbackInputConnection;
-            mUid = uid;
-            mPid = pid;
-            mSelfReportedDisplayId = selfReportedDisplayId;
-            mBinding = new InputBinding(null, mFallbackInputConnection.asBinder(), mUid, mPid);
-            mClientDeathRecipient = clientDeathRecipient;
-        }
-    }
-
-    @GuardedBy("ImfLock.class")
-    final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>();
+    /**
+     * Manages the IME clients.
+     */
+    private final ClientController mClientController;
 
     /**
      * Set once the system is ready to run third party code.
@@ -516,6 +479,7 @@
     /**
      * The client that is currently bound to an input method.
      */
+    // TODO(b/314150112): Move this to ClientController.
     @Nullable
     private ClientState mCurClient;
 
@@ -856,8 +820,9 @@
             @Nullable
             final String mImeSurfaceParentName;
 
-            Entry(ClientState client, EditorInfo editorInfo, String focusedWindowName,
-                    @SoftInputModeFlags int softInputMode, @SoftInputShowHideReason int reason,
+            Entry(ClientState client, EditorInfo editorInfo,
+                    String focusedWindowName, @SoftInputModeFlags int softInputMode,
+                    @SoftInputShowHideReason int reason,
                     boolean inFullscreenMode, String requestWindowName,
                     @Nullable String imeControlTargetName, @Nullable String imeTargetName,
                     @Nullable String imeSurfaceParentName) {
@@ -1621,7 +1586,7 @@
             if (userId != currentUserId) {
                 return;
             }
-            mSettings.switchCurrentUser(currentUserId);
+            mSettings = new InputMethodSettings(mMethodMap, userId);
             if (mSystemReady) {
                 // We need to rebuild IMEs.
                 buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
@@ -1699,8 +1664,10 @@
 
         AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
         mSwitchingController =
-                InputMethodSubtypeSwitchingController.createInstanceLocked(mSettings, context);
-        mHardwareKeyboardShortcutController.reset(mSettings);
+                InputMethodSubtypeSwitchingController.createInstanceLocked(context, mMethodMap,
+                        userId);
+        mHardwareKeyboardShortcutController =
+                new HardwareKeyboardShortcutController(mMethodMap, userId);
         mMenuController = new InputMethodMenuController(this);
         mBindingController =
                 bindingControllerForTesting != null
@@ -1710,6 +1677,7 @@
 
         mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
         mVisibilityApplier = new DefaultImeVisibilityApplier(this);
+        mClientController = new ClientController(mPackageManagerInternal);
 
         mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
                 com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
@@ -1821,11 +1789,7 @@
         // ContentObserver should be registered again when the user is changed
         mSettingsObserver.registerContentObserverLocked(newUserId);
 
-        // If the system is not ready or the device is not yed unlocked by the user, then we use
-        // copy-on-write settings.
-        final boolean useCopyOnWriteSettings =
-                !mSystemReady || !mUserManagerInternal.isUserUnlockingOrUnlocked(newUserId);
-        mSettings.switchCurrentUser(newUserId);
+        mSettings = new InputMethodSettings(mMethodMap, newUserId);
         // Additional subtypes should be reset when the user is changed
         AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, newUserId);
         final String defaultImiId = mSettings.getSelectedInputMethod();
@@ -1867,7 +1831,8 @@
         mLastSwitchUserId = newUserId;
 
         if (mIsInteractive && clientToBeReset != null) {
-            final ClientState cs = mClients.get(clientToBeReset.asBinder());
+            final ClientState cs =
+                    mClientController.mClients.get(clientToBeReset.asBinder());
             if (cs == null) {
                 // The client is already gone.
                 return;
@@ -1887,7 +1852,6 @@
             if (!mSystemReady) {
                 mSystemReady = true;
                 final int currentUserId = mSettings.getCurrentUserId();
-                mSettings.switchCurrentUser(currentUserId);
                 mStatusBarManagerInternal =
                         LocalServices.getService(StatusBarManagerInternal.class);
                 hideStatusBarIconLocked();
@@ -2205,43 +2169,22 @@
         // actually running.
         final int callerUid = Binder.getCallingUid();
         final int callerPid = Binder.getCallingPid();
+
+        // TODO(b/314150112): Move the death recipient logic to ClientController when moving
+        //     removeClient method.
+        final IBinder.DeathRecipient deathRecipient = () -> removeClient(client);
+        final IInputMethodClientInvoker clientInvoker =
+                IInputMethodClientInvoker.create(client, mHandler);
         synchronized (ImfLock.class) {
-            // TODO: Optimize this linear search.
-            final int numClients = mClients.size();
-            for (int i = 0; i < numClients; ++i) {
-                final ClientState state = mClients.valueAt(i);
-                if (state.mUid == callerUid && state.mPid == callerPid
-                        && state.mSelfReportedDisplayId == selfReportedDisplayId) {
-                    throw new SecurityException("uid=" + callerUid + "/pid=" + callerPid
-                            + "/displayId=" + selfReportedDisplayId + " is already registered.");
-                }
-            }
-            final ClientDeathRecipient deathRecipient = new ClientDeathRecipient(this, client);
-            try {
-                client.asBinder().linkToDeath(deathRecipient, 0 /* flags */);
-            } catch (RemoteException e) {
-                throw new IllegalStateException(e);
-            }
-            // We cannot fully avoid race conditions where the client UID already lost the access to
-            // the given self-reported display ID, even if the client is not maliciously reporting
-            // a fake display ID. Unconditionally returning SecurityException just because the
-            // client doesn't pass display ID verification can cause many test failures hence not an
-            // option right now.  At the same time
-            //    context.getSystemService(InputMethodManager.class)
-            // is expected to return a valid non-null instance at any time if we do not choose to
-            // have the client crash.  Thus we do not verify the display ID at all here.  Instead we
-            // later check the display ID every time the client needs to interact with the specified
-            // display.
-            final IInputMethodClientInvoker clientInvoker =
-                    IInputMethodClientInvoker.create(client, mHandler);
-            mClients.put(client.asBinder(), new ClientState(clientInvoker, inputConnection,
-                    callerUid, callerPid, selfReportedDisplayId, deathRecipient));
+            mClientController.addClient(clientInvoker, inputConnection, selfReportedDisplayId,
+                    deathRecipient, callerUid, callerPid);
         }
     }
 
+    // TODO(b/314150112): Move this to ClientController.
     void removeClient(IInputMethodClient client) {
         synchronized (ImfLock.class) {
-            ClientState cs = mClients.remove(client.asBinder());
+            ClientState cs = mClientController.mClients.remove(client.asBinder());
             if (cs != null) {
                 client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0 /* flags */);
                 clearClientSessionLocked(cs);
@@ -2271,6 +2214,7 @@
         }
     }
 
+    // TODO(b/314150112): Move this to ClientController.
     @GuardedBy("ImfLock.class")
     void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) {
         if (mCurClient != null) {
@@ -2323,7 +2267,10 @@
         }
     }
 
-    /** {@code true} when a {@link ClientState} has attached from starting the input connection. */
+    /**
+     * {@code true} when a {@link ClientState} has attached from starting the
+     * input connection.
+     */
     @GuardedBy("ImfLock.class")
     boolean hasAttachedClient() {
         return mCurClient != null;
@@ -2464,11 +2411,7 @@
             @StartInputReason int startInputReason,
             int unverifiedTargetSdkVersion,
             @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
-        // If no method is currently selected, do nothing.
-        final String selectedMethodId = getSelectedMethodIdLocked();
-        if (selectedMethodId == null) {
-            return InputBindResult.NO_IME;
-        }
+        String selectedMethodId = getSelectedMethodIdLocked();
 
         if (!mSystemReady) {
             // If the system is not yet ready, we shouldn't be running third
@@ -2493,8 +2436,21 @@
             return InputBindResult.NOT_IME_TARGET_WINDOW;
         }
         final int csDisplayId = cs.mSelfReportedDisplayId;
+        final int oldDisplayIdToShowIme = mDisplayIdToShowIme;
         mDisplayIdToShowIme = mVisibilityStateComputer.computeImeDisplayId(winState, csDisplayId);
 
+        // Potentially override the selected input method if the new display belongs to a virtual
+        // device with a custom IME.
+        if (oldDisplayIdToShowIme != mDisplayIdToShowIme) {
+            final String deviceMethodId = computeCurrentDeviceMethodIdLocked(selectedMethodId);
+            if (deviceMethodId == null) {
+                mVisibilityStateComputer.getImePolicy().setImeHiddenByDisplayPolicy(true);
+            } else if (!Objects.equals(deviceMethodId, selectedMethodId)) {
+                setInputMethodLocked(deviceMethodId, NOT_A_SUBTYPE_ID, mDeviceIdToShowIme);
+                selectedMethodId = deviceMethodId;
+            }
+        }
+
         if (mVisibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) {
             hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */,
                     null /* resultReceiver */,
@@ -2502,6 +2458,11 @@
             return InputBindResult.NO_IME;
         }
 
+        // If no method is currently selected, do nothing.
+        if (selectedMethodId == null) {
+            return InputBindResult.NO_IME;
+        }
+
         if (mCurClient != cs) {
             prepareClientSwitchLocked(cs);
         }
@@ -2568,6 +2529,62 @@
         return mBindingController.bindCurrentMethod();
     }
 
+    /**
+     * Update the current deviceId and return the relevant imeId for this device.
+     *   1. If the device changes to virtual and its custom IME is not available, then disable IME.
+     *   2. If the device changes to virtual with valid custom IME, then return the custom IME. If
+     *      the old device was default, then store the current imeId so it can be restored.
+     *   3. If the device changes to default, restore the default device IME.
+     *   4. Otherwise keep the current imeId.
+     */
+    @GuardedBy("ImfLock.class")
+    private String computeCurrentDeviceMethodIdLocked(String currentMethodId) {
+        if (mVdmInternal == null) {
+            mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class);
+        }
+        if (mVdmInternal == null || !android.companion.virtual.flags.Flags.vdmCustomIme()) {
+            return currentMethodId;
+        }
+
+        final int oldDeviceId = mDeviceIdToShowIme;
+        mDeviceIdToShowIme = mVdmInternal.getDeviceIdForDisplayId(mDisplayIdToShowIme);
+        if (mDeviceIdToShowIme == oldDeviceId) {
+            return currentMethodId;
+        }
+        if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+            final String defaultDeviceMethodId = mSettings.getSelectedDefaultDeviceInputMethod();
+            if (DEBUG) {
+                Slog.v(TAG, "Restoring default device input method: " + defaultDeviceMethodId);
+            }
+            return defaultDeviceMethodId;
+        }
+
+        final String deviceMethodId =
+                mVirtualDeviceMethodMap.get(mDeviceIdToShowIme, currentMethodId);
+        if (Objects.equals(deviceMethodId, currentMethodId)) {
+            return currentMethodId;
+        } else if (!mMethodMap.containsKey(deviceMethodId)) {
+            if (DEBUG) {
+                Slog.v(TAG, "Disabling IME on virtual device with id " + mDeviceIdToShowIme
+                        + " because its custom input method is not available: " + deviceMethodId);
+            }
+            return null;
+        }
+
+        if (oldDeviceId == DEVICE_ID_DEFAULT) {
+            if (DEBUG) {
+                Slog.v(TAG, "Storing default device input method " + currentMethodId);
+            }
+            mSettings.putSelectedDefaultDeviceInputMethod(currentMethodId);
+        }
+        if (DEBUG) {
+            Slog.v(TAG, "Switching current input method from " + currentMethodId
+                    + " to device-specific one " + deviceMethodId + " because the current display "
+                    + mDisplayIdToShowIme + " belongs to device with id " + mDeviceIdToShowIme);
+        }
+        return deviceMethodId;
+    }
+
     @GuardedBy("ImfLock.class")
     void invalidateAutofillSessionLocked() {
         mAutofillController.invalidateAutofillSession();
@@ -2897,10 +2914,10 @@
     @GuardedBy("ImfLock.class")
     void clearClientSessionsLocked() {
         if (getCurMethodLocked() != null) {
-            final int numClients = mClients.size();
+            final int numClients = mClientController.mClients.size();
             for (int i = 0; i < numClients; ++i) {
-                clearClientSessionLocked(mClients.valueAt(i));
-                clearClientSessionForAccessibilityLocked(mClients.valueAt(i));
+                clearClientSessionLocked(mClientController.mClients.valueAt(i));
+                clearClientSessionForAccessibilityLocked(mClientController.mClients.valueAt(i));
             }
 
             finishSessionLocked(mEnabledSession);
@@ -3218,13 +3235,21 @@
             // There is no longer an input method set, so stop any current one.
             resetCurrentMethodAndClientLocked(UnbindReason.NO_IME);
         }
-        // Here is not the perfect place to reset the switching controller. Ideally
-        // mSwitchingController and mSettings should be able to share the same state.
-        // TODO: Make sure that mSwitchingController and mSettings are sharing the
-        // the same enabled IMEs list.
-        mSwitchingController.resetCircularListLocked(mContext);
-        mHardwareKeyboardShortcutController.reset(mSettings);
 
+        // TODO: Instantiate mSwitchingController for each user.
+        if (mSettings.getCurrentUserId() == mSwitchingController.getUserId()) {
+            mSwitchingController.resetCircularListLocked(mMethodMap);
+        } else {
+            mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
+                    mContext, mMethodMap, mSettings.getCurrentUserId());
+        }
+        // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
+        if (mSettings.getCurrentUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+            mHardwareKeyboardShortcutController.reset(mMethodMap);
+        } else {
+            mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
+                    mMethodMap, mSettings.getCurrentUserId());
+        }
         sendOnNavButtonFlagsChangedLocked();
     }
 
@@ -3242,6 +3267,11 @@
 
     @GuardedBy("ImfLock.class")
     void setInputMethodLocked(String id, int subtypeId) {
+        setInputMethodLocked(id, subtypeId, DEVICE_ID_DEFAULT);
+    }
+
+    @GuardedBy("ImfLock.class")
+    void setInputMethodLocked(String id, int subtypeId, int deviceId) {
         InputMethodInfo info = mMethodMap.get(id);
         if (info == null) {
             throw getExceptionForUnknownImeId(id);
@@ -3285,6 +3315,14 @@
         }
 
         // Changing to a different IME.
+        if (mDeviceIdToShowIme != DEVICE_ID_DEFAULT && deviceId == DEVICE_ID_DEFAULT) {
+            // This change should only be applicable to the default device but the current input
+            // method is a custom one specific to a virtual device. So only update the settings
+            // entry used to restore the default device input method once we want to show the IME
+            // back on the default device.
+            mSettings.putSelectedDefaultDeviceInputMethod(id);
+            return;
+        }
         IInputMethodInvoker curMethod = getCurMethodLocked();
         if (curMethod != null) {
             curMethod.removeStylusHandwritingWindow();
@@ -3414,9 +3452,12 @@
                     + " pref is disabled for user: " + userId);
             return;
         }
-        if (!verifyClientAndPackageMatch(client, delegatorPackageName)) {
-            Slog.w(TAG, "prepareStylusHandwritingDelegation() fail");
-            throw new IllegalArgumentException("Delegator doesn't match Uid");
+        synchronized (ImfLock.class) {
+            if (!mClientController.verifyClientAndPackageMatch(client,
+                    delegatorPackageName)) {
+                Slog.w(TAG, "prepareStylusHandwritingDelegation() fail");
+                throw new IllegalArgumentException("Delegator doesn't match Uid");
+            }
         }
         schedulePrepareStylusHandwritingDelegation(
                 userId, delegatePackageName, delegatorPackageName);
@@ -3442,30 +3483,17 @@
         return true;
     }
 
-    private boolean verifyClientAndPackageMatch(
-            @NonNull IInputMethodClient client, @NonNull String packageName) {
-        ClientState cs;
-        synchronized (ImfLock.class) {
-            cs = mClients.get(client.asBinder());
-        }
-        if (cs == null) {
-            throw new IllegalArgumentException("unknown client " + client.asBinder());
-        }
-        return InputMethodUtils.checkIfPackageBelongsToUid(
-                mPackageManagerInternal, cs.mUid, packageName);
-    }
-
     private boolean verifyDelegator(
             @NonNull IInputMethodClient client,
             @NonNull String delegatePackageName,
             @NonNull String delegatorPackageName,
             @InputMethodManager.HandwritingDelegateFlags int flags) {
-        if (!verifyClientAndPackageMatch(client, delegatePackageName)) {
-            Slog.w(TAG, "Delegate package does not belong to the same user. Ignoring"
-                    + " startStylusHandwriting");
-            return false;
-        }
         synchronized (ImfLock.class) {
+            if (!mClientController.verifyClientAndPackageMatch(client, delegatePackageName)) {
+                Slog.w(TAG, "Delegate package does not belong to the same user. Ignoring"
+                        + " startStylusHandwriting");
+                return false;
+            }
             boolean homeDelegatorAllowed =
                     (flags & InputMethodManager.HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED)
                             != 0;
@@ -3728,7 +3756,7 @@
             return InputBindResult.INVALID_USER;
         }
 
-        final ClientState cs = mClients.get(client.asBinder());
+        final ClientState cs = mClientController.mClients.get(client.asBinder());
         if (cs == null) {
             throw new IllegalArgumentException("unknown client " + client.asBinder());
         }
@@ -3902,7 +3930,8 @@
             // We need to check if this is the current client with
             // focus in the window manager, to allow this call to
             // be made before input is started in it.
-            final ClientState cs = mClients.get(client.asBinder());
+            final ClientState cs =
+                    mClientController.mClients.get(client.asBinder());
             if (cs == null) {
                 ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
                 throw new IllegalArgumentException("unknown client " + client.asBinder());
@@ -4526,7 +4555,7 @@
         ImeTracing.getInstance().startTrace(null /* printwriter */);
         ArrayMap<IBinder, ClientState> clients;
         synchronized (ImfLock.class) {
-            clients = new ArrayMap<>(mClients);
+            clients = new ArrayMap<>(mClientController.mClients);
         }
         for (ClientState state : clients.values()) {
             if (state != null) {
@@ -4544,7 +4573,7 @@
         ImeTracing.getInstance().stopTrace(null /* printwriter */);
         ArrayMap<IBinder, ClientState> clients;
         synchronized (ImfLock.class) {
-            clients = new ArrayMap<>(mClients);
+            clients = new ArrayMap<>(mClientController.mClients);
         }
         for (ClientState state : clients.values()) {
             if (state != null) {
@@ -4598,6 +4627,9 @@
                 }
                 return;
             }
+            if (mSettings.getCurrentUserId() != mSwitchingController.getUserId()) {
+                return;
+            }
             final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked());
             if (imi != null) {
                 mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
@@ -4835,9 +4867,10 @@
                     int lastInputMethodSubtypeId =
                             mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
 
-                    final List<ImeSubtypeListItem> imList = mSwitchingController
-                            .getSortedInputMethodAndSubtypeListForImeMenuLocked(
-                                    showAuxSubtypes, isScreenLocked);
+                    final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController
+                            .getSortedInputMethodAndSubtypeList(
+                                    showAuxSubtypes, isScreenLocked, false, mContext,
+                                    mMethodMap, mSettings.getCurrentUserId());
                     mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
                             lastInputMethodId, lastInputMethodSubtypeId, imList);
                 }
@@ -5222,12 +5255,20 @@
 
         updateDefaultVoiceImeIfNeededLocked();
 
-        // Here is not the perfect place to reset the switching controller. Ideally
-        // mSwitchingController and mSettings should be able to share the same state.
-        // TODO: Make sure that mSwitchingController and mSettings are sharing the
-        // the same enabled IMEs list.
-        mSwitchingController.resetCircularListLocked(mContext);
-        mHardwareKeyboardShortcutController.reset(mSettings);
+        // TODO: Instantiate mSwitchingController for each user.
+        if (mSettings.getCurrentUserId() == mSwitchingController.getUserId()) {
+            mSwitchingController.resetCircularListLocked(mMethodMap);
+        } else {
+            mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
+                    mContext, mMethodMap, mSettings.getCurrentUserId());
+        }
+        // TODO: Instantiate mHardwareKeyboardShortcutController for each user.
+        if (mSettings.getCurrentUserId() == mHardwareKeyboardShortcutController.getUserId()) {
+            mHardwareKeyboardShortcutController.reset(mMethodMap);
+        } else {
+            mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(
+                    mMethodMap, mSettings.getCurrentUserId());
+        }
 
         sendOnNavButtonFlagsChangedLocked();
 
@@ -5308,11 +5349,21 @@
             StringBuilder builder = new StringBuilder();
             if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
                     builder, enabledInputMethodsList, id)) {
-                // Disabled input method is currently selected, switch to another one.
-                final String selId = mSettings.getSelectedInputMethod();
-                if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
-                    Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
-                    resetSelectedInputMethodAndSubtypeLocked("");
+                if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) {
+                    // Disabled input method is currently selected, switch to another one.
+                    final String selId = mSettings.getSelectedInputMethod();
+                    if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
+                        Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
+                        resetSelectedInputMethodAndSubtypeLocked("");
+                    }
+                } else if (id.equals(mSettings.getSelectedDefaultDeviceInputMethod())) {
+                    // Disabled default device IME while using a virtual device one, choose a
+                    // new default one but only update the settings.
+                    InputMethodInfo newDefaultIme =
+                            InputMethodInfoUtils.getMostApplicableDefaultIME(
+                                        mSettings.getEnabledInputMethodListLocked());
+                    mSettings.putSelectedDefaultDeviceInputMethod(
+                            newDefaultIme == null ? "" : newDefaultIme.getId());
                 }
                 // Previous state was enabled.
                 return true;
@@ -5652,9 +5703,8 @@
 
         @Override
         public void setVirtualDeviceInputMethodForAllUsers(int deviceId, @Nullable String imeId) {
-            // TODO(b/287269288): validate that id belongs to a valid virtual device instead.
-            Preconditions.checkArgument(deviceId != Context.DEVICE_ID_DEFAULT,
-                    "DeviceId " + deviceId + " does not belong to a virtual device.");
+            Preconditions.checkArgument(deviceId != DEVICE_ID_DEFAULT,
+                    TextUtils.formatSimple("DeviceId %d is not a virtual device id.", deviceId));
             synchronized (ImfLock.class) {
                 if (imeId == null) {
                     mVirtualDeviceMethodMap.remove(deviceId);
@@ -5768,10 +5818,10 @@
                 // We only have sessions when we bound to an input method. Remove this session
                 // from all clients.
                 if (getCurMethodLocked() != null) {
-                    final int numClients = mClients.size();
+                    final int numClients = mClientController.mClients.size();
                     for (int i = 0; i < numClients; ++i) {
-                        clearClientSessionForAccessibilityLocked(mClients.valueAt(i),
-                                accessibilityConnectionId);
+                        clearClientSessionForAccessibilityLocked(
+                                mClientController.mClients.valueAt(i), accessibilityConnectionId);
                     }
                     AccessibilitySessionState session = mEnabledAccessibilitySessions.get(
                             accessibilityConnectionId);
@@ -5956,9 +6006,10 @@
                 info.dump(p, "    ");
             }
             p.println("  ClientStates:");
-            final int numClients = mClients.size();
+            // TODO(b/314150112): move client related dump info to ClientController#dump
+            final int numClients = mClientController.mClients.size();
             for (int i = 0; i < numClients; ++i) {
-                final ClientState ci = mClients.valueAt(i);
+                final ClientState ci = mClientController.mClients.valueAt(i);
                 p.println("  " + ci + ":");
                 p.println("    client=" + ci.mClient);
                 p.println("    fallbackInputConnection=" + ci.mFallbackInputConnection);
@@ -6577,7 +6628,7 @@
         boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled();
         ArrayMap<IBinder, ClientState> clients;
         synchronized (ImfLock.class) {
-            clients = new ArrayMap<>(mClients);
+            clients = new ArrayMap<>(mClientController.mClients);
         }
         for (ClientState state : clients.values()) {
             if (state != null) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 431aabd..4439b06 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -16,10 +16,14 @@
 
 package com.android.server.inputmethod;
 
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.Context;
-import android.content.pm.PackageManager;
+import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Printer;
 import android.util.Slog;
@@ -32,7 +36,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Locale;
 import java.util.Objects;
 
 /**
@@ -154,79 +157,71 @@
         }
     }
 
-    private static class InputMethodAndSubtypeList {
-        private final Context mContext;
-        // Used to load label
-        private final PackageManager mPm;
-        private final String mSystemLocaleStr;
-        private final InputMethodSettings mSettings;
+    static List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(
+            boolean includeAuxiliarySubtypes, boolean isScreenLocked, boolean forImeMenu,
+            @NonNull Context context, @NonNull ArrayMap<String, InputMethodInfo> methodMap,
+            @UserIdInt int userId) {
+        final Context userAwareContext = context.getUserId() == userId
+                ? context
+                : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
+        final String mSystemLocaleStr = SystemLocaleWrapper.get(userId).get(0).toLanguageTag();
+        final InputMethodSettings settings = new InputMethodSettings(methodMap, userId);
 
-        InputMethodAndSubtypeList(Context context, InputMethodSettings settings) {
-            mContext = context;
-            mSettings = settings;
-            mPm = context.getPackageManager();
-            final Locale locale = context.getResources().getConfiguration().locale;
-            mSystemLocaleStr = locale != null ? locale.toString() : "";
+        final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodListLocked();
+        if (imis.isEmpty()) {
+            return Collections.emptyList();
         }
-
-        public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(
-                boolean includeAuxiliarySubtypes, boolean isScreenLocked, boolean forImeMenu) {
-            final ArrayList<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
-            if (imis.isEmpty()) {
-                return Collections.emptyList();
+        if (isScreenLocked && includeAuxiliarySubtypes) {
+            if (DEBUG) {
+                Slog.w(TAG, "Auxiliary subtypes are not allowed to be shown in lock screen.");
             }
-            if (isScreenLocked && includeAuxiliarySubtypes) {
+            includeAuxiliarySubtypes = false;
+        }
+        final ArrayList<ImeSubtypeListItem> imList = new ArrayList<>();
+        final int numImes = imis.size();
+        for (int i = 0; i < numImes; ++i) {
+            final InputMethodInfo imi = imis.get(i);
+            if (forImeMenu && !imi.shouldShowInInputMethodPicker()) {
+                continue;
+            }
+            final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList =
+                    settings.getEnabledInputMethodSubtypeListLocked(imi, true);
+            final ArraySet<String> enabledSubtypeSet = new ArraySet<>();
+            for (InputMethodSubtype subtype : explicitlyOrImplicitlyEnabledSubtypeList) {
+                enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
+            }
+            final CharSequence imeLabel = imi.loadLabel(userAwareContext.getPackageManager());
+            if (enabledSubtypeSet.size() > 0) {
+                final int subtypeCount = imi.getSubtypeCount();
                 if (DEBUG) {
-                    Slog.w(TAG, "Auxiliary subtypes are not allowed to be shown in lock screen.");
+                    Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
                 }
-                includeAuxiliarySubtypes = false;
-            }
-            final ArrayList<ImeSubtypeListItem> imList = new ArrayList<>();
-            final int numImes = imis.size();
-            for (int i = 0; i < numImes; ++i) {
-                final InputMethodInfo imi = imis.get(i);
-                if (forImeMenu && !imi.shouldShowInInputMethodPicker()) {
-                    continue;
-                }
-                final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList =
-                        mSettings.getEnabledInputMethodSubtypeListLocked(imi, true);
-                final ArraySet<String> enabledSubtypeSet = new ArraySet<>();
-                for (InputMethodSubtype subtype : explicitlyOrImplicitlyEnabledSubtypeList) {
-                    enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
-                }
-                final CharSequence imeLabel = imi.loadLabel(mPm);
-                if (enabledSubtypeSet.size() > 0) {
-                    final int subtypeCount = imi.getSubtypeCount();
-                    if (DEBUG) {
-                        Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
-                    }
-                    for (int j = 0; j < subtypeCount; ++j) {
-                        final InputMethodSubtype subtype = imi.getSubtypeAt(j);
-                        final String subtypeHashCode = String.valueOf(subtype.hashCode());
-                        // We show all enabled IMEs and subtypes when an IME is shown.
-                        if (enabledSubtypeSet.contains(subtypeHashCode)
-                                && (includeAuxiliarySubtypes || !subtype.isAuxiliary())) {
-                            final CharSequence subtypeLabel =
-                                    subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
-                                            .getDisplayName(mContext, imi.getPackageName(),
-                                                    imi.getServiceInfo().applicationInfo);
-                            imList.add(new ImeSubtypeListItem(imeLabel,
-                                    subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
+                for (int j = 0; j < subtypeCount; ++j) {
+                    final InputMethodSubtype subtype = imi.getSubtypeAt(j);
+                    final String subtypeHashCode = String.valueOf(subtype.hashCode());
+                    // We show all enabled IMEs and subtypes when an IME is shown.
+                    if (enabledSubtypeSet.contains(subtypeHashCode)
+                            && (includeAuxiliarySubtypes || !subtype.isAuxiliary())) {
+                        final CharSequence subtypeLabel =
+                                subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
+                                        .getDisplayName(userAwareContext, imi.getPackageName(),
+                                                imi.getServiceInfo().applicationInfo);
+                        imList.add(new ImeSubtypeListItem(imeLabel,
+                                subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
 
-                            // Removing this subtype from enabledSubtypeSet because we no
-                            // longer need to add an entry of this subtype to imList to avoid
-                            // duplicated entries.
-                            enabledSubtypeSet.remove(subtypeHashCode);
-                        }
+                        // Removing this subtype from enabledSubtypeSet because we no
+                        // longer need to add an entry of this subtype to imList to avoid
+                        // duplicated entries.
+                        enabledSubtypeSet.remove(subtypeHashCode);
                     }
-                } else {
-                    imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null,
-                            mSystemLocaleStr));
                 }
+            } else {
+                imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null,
+                        mSystemLocaleStr));
             }
-            Collections.sort(imList);
-            return imList;
         }
+        Collections.sort(imList);
+        return imList;
     }
 
     private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) {
@@ -479,18 +474,32 @@
         }
     }
 
-    private final InputMethodSettings mSettings;
-    private InputMethodAndSubtypeList mSubtypeList;
+    private final Context mContext;
+    @UserIdInt
+    private final int mUserId;
     private ControllerImpl mController;
 
-    private InputMethodSubtypeSwitchingController(InputMethodSettings settings, Context context) {
-        mSettings = settings;
-        resetCircularListLocked(context);
+    private InputMethodSubtypeSwitchingController(@NonNull Context context,
+            @NonNull ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
+        mContext = context;
+        mUserId = userId;
+        mController = ControllerImpl.createFrom(null,
+                getSortedInputMethodAndSubtypeList(
+                        false /* includeAuxiliarySubtypes */, false /* isScreenLocked */,
+                        false /* forImeMenu */, context, methodMap, userId));
     }
 
+    @NonNull
     public static InputMethodSubtypeSwitchingController createInstanceLocked(
-            InputMethodSettings settings, Context context) {
-        return new InputMethodSubtypeSwitchingController(settings, context);
+            @NonNull Context context,
+            @NonNull ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
+        return new InputMethodSubtypeSwitchingController(context, methodMap, userId);
+    }
+
+    @AnyThread
+    @UserIdInt
+    int getUserId() {
+        return mUserId;
     }
 
     public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) {
@@ -503,12 +512,12 @@
         mController.onUserActionLocked(imi, subtype);
     }
 
-    public void resetCircularListLocked(Context context) {
-        mSubtypeList = new InputMethodAndSubtypeList(context, mSettings);
+    public void resetCircularListLocked(
+            @NonNull ArrayMap<String, InputMethodInfo> methodMap) {
         mController = ControllerImpl.createFrom(mController,
-                mSubtypeList.getSortedInputMethodAndSubtypeList(
+                getSortedInputMethodAndSubtypeList(
                         false /* includeAuxiliarySubtypes */, false /* isScreenLocked */,
-                        false /* forImeMenu */));
+                        false /* forImeMenu */, mContext, methodMap, mUserId));
     }
 
     public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi,
@@ -522,12 +531,6 @@
         return mController.getNextInputMethod(onlyCurrentIme, imi, subtype);
     }
 
-    public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListForImeMenuLocked(
-            boolean includingAuxiliarySubtypes, boolean isScreenLocked) {
-        return mSubtypeList.getSortedInputMethodAndSubtypeList(
-                includingAuxiliarySubtypes, isScreenLocked, true /* forImeMenu */);
-    }
-
     public void dump(final Printer pw) {
         if (mController != null) {
             mController.dump(pw);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index a0b55ed..fb57c09 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -51,6 +51,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.StringJoiner;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -214,7 +215,7 @@
         private final ArrayMap<String, InputMethodInfo> mMethodMap;
 
         @UserIdInt
-        private int mCurrentUserId;
+        private final int mCurrentUserId;
 
         private static void buildEnabledInputMethodsSettingString(
                 StringBuilder builder, Pair<String, ArrayList<String>> ime) {
@@ -228,19 +229,13 @@
 
         InputMethodSettings(ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
             mMethodMap = methodMap;
-            switchCurrentUser(userId);
-        }
-
-        /**
-         * Must be called when the current user is changed.
-         *
-         * @param userId The user ID.
-         */
-        void switchCurrentUser(@UserIdInt int userId) {
-            if (DEBUG) {
-                Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId);
-            }
             mCurrentUserId = userId;
+            String ime = getSelectedInputMethod();
+            String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
+            if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
+                putSelectedInputMethod(defaultDeviceIme);
+                putSelectedDefaultDeviceInputMethod(null);
+            }
         }
 
         private void putString(@NonNull String key, @Nullable String str) {
@@ -636,6 +631,24 @@
             return imi;
         }
 
+        @Nullable
+        String getSelectedDefaultDeviceInputMethod() {
+            final String imi = getString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null);
+            if (DEBUG) {
+                Slog.d(TAG, "getSelectedDefaultDeviceInputMethodStr: " + imi + ", "
+                        + mCurrentUserId);
+            }
+            return imi;
+        }
+
+        void putSelectedDefaultDeviceInputMethod(String imeId) {
+            if (DEBUG) {
+                Slog.d(TAG, "putSelectedDefaultDeviceInputMethodStr: " + imeId + ", "
+                        + mCurrentUserId);
+            }
+            putString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, imeId);
+        }
+
         void putDefaultVoiceInputMethod(String imeId) {
             if (DEBUG) {
                 Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId);
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 41d0176..22951d5 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -180,7 +180,9 @@
         // priority of system overlays.
         final ArrayMap<String, File> apkInApexPreInstalledPaths = new ArrayMap<>();
         for (ApexManager.ActiveApexInfo apexInfo : mApexManager.getActiveApexInfos()) {
-            for (String packageName : mApexManager.getApksInApex(apexInfo.apexModuleName)) {
+            final String apexPackageName = mApexManager.getActivePackageNameForApexModuleName(
+                    apexInfo.apexModuleName);
+            for (String packageName : mApexManager.getApksInApex(apexPackageName)) {
                 apkInApexPreInstalledPaths.put(packageName, apexInfo.preInstalledApexPath);
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index dcfc855d..376b061 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -25,6 +25,7 @@
 import static android.content.pm.ArchivedActivityInfo.bytesFromBitmap;
 import static android.content.pm.ArchivedActivityInfo.drawableToBitmap;
 import static android.content.pm.PackageInstaller.EXTRA_UNARCHIVE_STATUS;
+import static android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION;
 import static android.content.pm.PackageInstaller.UNARCHIVAL_OK;
 import static android.content.pm.PackageManager.DELETE_ARCHIVE;
 import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
@@ -72,10 +73,12 @@
 import android.os.IBinder;
 import android.os.ParcelableException;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.SELinux;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ExceptionUtils;
+import android.util.Pair;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -93,7 +96,9 @@
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 
@@ -140,15 +145,23 @@
     private final Context mContext;
     private final PackageManagerService mPm;
 
+    private final AppStateHelper mAppStateHelper;
+
     @Nullable
     private LauncherApps mLauncherApps;
 
     @Nullable
     private AppOpsManager mAppOpsManager;
 
+    /* IntentSender store that maps key: {userId, appPackageName} to respective existing attached
+     unarchival intent sender. */
+    private final Map<Pair<Integer, String>, IntentSender> mLauncherIntentSenders;
+
     PackageArchiver(Context context, PackageManagerService mPm) {
         this.mContext = context;
         this.mPm = mPm;
+        this.mAppStateHelper = new AppStateHelper(mContext);
+        this.mLauncherIntentSenders = new HashMap<>();
     }
 
     /** Returns whether a package is archived for a user. */
@@ -235,37 +248,32 @@
             // Return early as the calling UID does not match caller package's UID.
             return START_CLASS_NOT_FOUND;
         }
+
         String currentLauncherPackageName = getCurrentLauncherPackageName(userId);
         if ((currentLauncherPackageName == null || !callerPackageName.equals(
                 currentLauncherPackageName)) && callingUid != Process.SHELL_UID) {
             // TODO(b/311619990): Remove dependency on SHELL_UID for testing
             Slog.e(TAG, TextUtils.formatSimple(
-                    "callerPackageName: %s does not qualify for archival of package: " + "%s!",
+                    "callerPackageName: %s does not qualify for unarchival of package: " + "%s!",
                     callerPackageName, packageName));
             return START_PERMISSION_DENIED;
         }
-        // TODO(b/302114464): Handle edge cases & also divert to a dialog based on
-        //  permissions + compat options
-        Slog.i(TAG, TextUtils.formatSimple("Unarchival is starting for: %s", packageName));
-        try {
-            final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
-                @Override
-                public void send(int code, Intent intent, String resolvedType,
-                        IBinder allowlistToken,
-                        IIntentReceiver finishedReceiver, String requiredPermission,
-                        Bundle options) {
-                    // TODO(b/302114464): Handle intent sender status codes
-                }
-            };
 
+        Slog.i(TAG, TextUtils.formatSimple("Unarchival is starting for: %s", packageName));
+
+        try {
+            // TODO(b/311709794) Make showUnarchivalConfirmation dependent on the compat options.
             requestUnarchive(packageName, callerPackageName,
-                    new IntentSender((IIntentSender) mLocalSender), UserHandle.of(userId));
+                    getOrCreateUnarchiveIntentSender(userId, packageName),
+                    UserHandle.of(userId),
+                    false /* showUnarchivalConfirmation= */);
         } catch (Throwable t) {
             Slog.e(TAG, TextUtils.formatSimple(
                     "Unexpected error occurred while unarchiving package %s: %s.", packageName,
                     t.getLocalizedMessage()));
             return START_ABORTED;
         }
+
         return START_SUCCESS;
     }
 
@@ -321,6 +329,20 @@
         return true;
     }
 
+    private IntentSender getOrCreateUnarchiveIntentSender(int userId, String packageName) {
+        Pair<Integer, String> key = Pair.create(userId, packageName);
+        synchronized (mLauncherIntentSenders) {
+            IntentSender intentSender = mLauncherIntentSenders.get(key);
+            if (intentSender != null) {
+                return intentSender;
+            }
+            IntentSender unarchiveIntentSender = new IntentSender(
+                    (IIntentSender) new UnarchiveIntentSender());
+            mLauncherIntentSenders.put(key, unarchiveIntentSender);
+            return unarchiveIntentSender;
+        }
+    }
+
     /** Creates archived state for the package and user. */
     private CompletableFuture<ArchiveState> createArchiveState(String packageName, int userId)
             throws PackageManager.NameNotFoundException {
@@ -553,6 +575,15 @@
             @NonNull String callerPackageName,
             @NonNull IntentSender statusReceiver,
             @NonNull UserHandle userHandle) {
+        requestUnarchive(packageName, callerPackageName, statusReceiver, userHandle,
+                false /* showUnarchivalConfirmation= */);
+    }
+
+    private void requestUnarchive(
+            @NonNull String packageName,
+            @NonNull String callerPackageName,
+            @NonNull IntentSender statusReceiver,
+            @NonNull UserHandle userHandle, boolean showUnarchivalConfirmation) {
         Objects.requireNonNull(packageName);
         Objects.requireNonNull(callerPackageName);
         Objects.requireNonNull(statusReceiver);
@@ -597,8 +628,8 @@
                     + "an unarchival.");
         }
 
-        if (!hasInstallPackages) {
-            requestUnarchiveConfirmation(packageName, statusReceiver);
+        if (!hasInstallPackages || showUnarchivalConfirmation) {
+            requestUnarchiveConfirmation(packageName, statusReceiver, userHandle);
             return;
         }
 
@@ -622,7 +653,8 @@
                 () -> unarchiveInternal(packageName, userHandle, installerPackage, draftSessionId));
     }
 
-    private void requestUnarchiveConfirmation(String packageName, IntentSender statusReceiver) {
+    private void requestUnarchiveConfirmation(String packageName, IntentSender statusReceiver,
+            UserHandle user) {
         final Intent dialogIntent = new Intent(ACTION_UNARCHIVE_DIALOG);
         dialogIntent.putExtra(EXTRA_UNARCHIVE_INTENT_SENDER, statusReceiver);
         dialogIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName);
@@ -632,6 +664,7 @@
         broadcastIntent.putExtra(EXTRA_UNARCHIVE_STATUS,
                 PackageInstaller.STATUS_PENDING_USER_ACTION);
         broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent);
+        broadcastIntent.putExtra(Intent.EXTRA_USER, user);
         sendIntent(statusReceiver, packageName, /* message= */ "", broadcastIntent);
     }
 
@@ -656,6 +689,7 @@
 
         int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
         // Handles case of repeated unarchival calls for the same package.
+        // TODO(b/316881759) Allow attaching multiple intentSenders to one session.
         int existingSessionId = mPm.mInstallerService.getExistingDraftSessionId(installerUid,
                 sessionParams,
                 userId);
@@ -849,7 +883,13 @@
 
     void notifyUnarchivalListener(int status, String installerPackageName, String appPackageName,
             long requiredStorageBytes, @Nullable PendingIntent userActionIntent,
-            IntentSender unarchiveIntentSender, int userId) {
+            @Nullable IntentSender unarchiveIntentSender, int userId) {
+        if (unarchiveIntentSender == null) {
+            // Maybe this can happen if the installer calls reportUnarchivalStatus twice in quick
+            // succession.
+            return;
+        }
+
         final Intent broadcastIntent = new Intent();
         broadcastIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, appPackageName);
         broadcastIntent.putExtra(EXTRA_UNARCHIVE_STATUS, status);
@@ -863,6 +903,7 @@
                 return;
             }
             broadcastIntent.putExtra(Intent.EXTRA_INTENT, dialogIntent);
+            broadcastIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
         }
 
         final BroadcastOptions options = BroadcastOptions.makeBasic();
@@ -874,6 +915,10 @@
                     options.toBundle());
         } catch (IntentSender.SendIntentException e) {
             Slog.e(TAG, TextUtils.formatSimple("Failed to send unarchive intent"), e);
+        } finally {
+            synchronized (mLauncherIntentSenders) {
+                mLauncherIntentSenders.remove(Pair.create(userId, appPackageName));
+            }
         }
     }
 
@@ -883,6 +928,7 @@
             long requiredStorageBytes, PendingIntent userActionIntent, int userId) {
         final Intent dialogIntent = new Intent(ACTION_UNARCHIVE_ERROR_DIALOG);
         dialogIntent.putExtra(EXTRA_UNARCHIVE_STATUS, status);
+        dialogIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
         if (requiredStorageBytes > 0) {
             dialogIntent.putExtra(EXTRA_REQUIRED_BYTES, requiredStorageBytes);
         }
@@ -1118,4 +1164,25 @@
 
         return activities.toArray(new ArchivedActivityParcel[activities.size()]);
     }
+
+    private class UnarchiveIntentSender extends IIntentSender.Stub {
+        @Override
+        public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+                IIntentReceiver finishedReceiver, String requiredPermission, Bundle options)
+                throws RemoteException {
+            int status = intent.getExtras().getInt(PackageInstaller.EXTRA_UNARCHIVE_STATUS,
+                    STATUS_PENDING_USER_ACTION);
+            if (status == UNARCHIVAL_OK) {
+                return;
+            }
+            Intent extraIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent.class);
+            UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER, UserHandle.class);
+            if (extraIntent != null && user != null
+                    && mAppStateHelper.isAppTopVisible(
+                            getCurrentLauncherPackageName(user.getIdentifier()))) {
+                extraIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                mContext.startActivityAsUser(extraIntent, user);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index cbd65a4..0a23dfb 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -291,9 +291,7 @@
     @NonNull
     private final RequestThrottle mSettingsWriteRequest = new RequestThrottle(IoThread.getHandler(),
             () -> {
-                synchronized (mSessions) {
-                    return writeSessionsLocked();
-                }
+                return writeSessions();
             });
 
     public PackageInstallerService(Context context, PackageManagerService pm,
@@ -600,9 +598,16 @@
                 mHistoricalSessionsByInstaller.get(installerUid) + 1);
     }
 
-    @GuardedBy("mSessions")
-    private boolean writeSessionsLocked() {
-        if (LOGD) Slog.v(TAG, "writeSessionsLocked()");
+    private boolean writeSessions() {
+        if (LOGD) Slog.v(TAG, "writeSessions()");
+        final PackageInstallerSession[] sessions;
+        synchronized (mSessions) {
+            final int size = mSessions.size();
+            sessions = new PackageInstallerSession[size];
+            for (int i = 0; i < size; i++) {
+                sessions[i] = mSessions.valueAt(i);
+            }
+        }
 
         FileOutputStream fos = null;
         try {
@@ -611,9 +616,7 @@
             final TypedXmlSerializer out = Xml.resolveSerializer(fos);
             out.startDocument(null, true);
             out.startTag(null, TAG_SESSIONS);
-            final int size = mSessions.size();
-            for (int i = 0; i < size; i++) {
-                final PackageInstallerSession session = mSessions.valueAt(i);
+            for (var session : sessions) {
                 session.write(out, mSessionsDir);
             }
             out.endTag(null, TAG_SESSIONS);
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 02e28dd..7bd6a43 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -487,6 +487,8 @@
         synchronized (mPm.mInstallLock) {
             cleanUpResourcesLI(codeFile, instructionSets);
         }
+        // TODO: open logging to help debug, will delete or add debug flag
+        Slog.d(TAG, "cleanUpResources for " + codeFile);
         if (packageName == null) {
             return;
         }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index edce3ec..f651dbf 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4211,6 +4211,14 @@
         return redoLayout;
     }
 
+    /**
+     * Shows the keyguard without immediately locking the device.
+     */
+    @Override
+    public void showDismissibleKeyguard() {
+        mKeyguardDelegate.showDismissibleKeyguard();
+    }
+
     // There are several different flavors of "assistant" that can be launched from
     // various parts of the UI.
 
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 3016b39..2174fd6 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -178,6 +178,12 @@
     int applyKeyguardOcclusionChange();
 
     /**
+     * Shows the keyguard immediately if not already shown.
+     * Does NOT immediately request the device to lock.
+     */
+    void showDismissibleKeyguard();
+
+    /**
      * Interface to the Window Manager state associated with a particular
      * window. You can hold on to an instance of this interface from the call
      * to prepareAddWindow() until removeWindow().
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 495e239..d0b70c3 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -410,6 +410,15 @@
         }
     }
 
+    /**
+     * Request to show the keyguard immediately without immediately locking the device.
+     */
+    public void showDismissibleKeyguard() {
+        if (mKeyguardService != null) {
+            mKeyguardService.showDismissibleKeyguard();
+        }
+    }
+
     public void setCurrentUser(int newUserId) {
         if (mKeyguardService != null) {
             mKeyguardService.setCurrentUser(newUserId);
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
index 774e261..cd789ea 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -209,6 +209,16 @@
         }
     }
 
+    // Binder interface
+    @Override
+    public void showDismissibleKeyguard() {
+        try {
+            mService.showDismissibleKeyguard();
+        } catch (RemoteException e) {
+            Slog.w(TAG , "Remote Exception", e);
+        }
+    }
+
     @Override // Binder interface
     public void setSwitchingUser(boolean switching) {
         try {
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 3ecc985..652cf18 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -411,6 +411,11 @@
         mWakeLockLog.onWakeLockReleased(tag, ownerUid);
     }
 
+    /** Shows the keyguard without requesting the device to immediately lock. */
+    public void showDismissibleKeyguard() {
+        mPolicy.showDismissibleKeyguard();
+    }
+
     private int getBatteryStatsWakeLockMonitorType(int flags) {
         switch (flags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
             case PowerManager.PARTIAL_WAKE_LOCK:
@@ -958,7 +963,8 @@
             final boolean vibrate = Settings.Secure.getIntForUser(mContext.getContentResolver(),
                     Settings.Secure.CHARGING_VIBRATION_ENABLED, 1, userId) != 0;
             if (vibrate) {
-                mVibrator.vibrate(CHARGING_VIBRATION_EFFECT,
+                mVibrator.vibrate(Process.SYSTEM_UID, mContext.getOpPackageName(),
+                        CHARGING_VIBRATION_EFFECT, /* reason= */ "Charging started",
                         HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
             }
 
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index ec5172f..2128c991 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -115,6 +115,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.display.BrightnessSynchronizer;
+import com.android.internal.foldables.FoldGracePeriodProvider;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FrameworkStatsLog;
@@ -308,6 +309,7 @@
     private final Context mContext;
     private final ServiceThread mHandlerThread;
     private final Handler mHandler;
+    private final FoldGracePeriodProvider mFoldGracePeriodProvider;
     private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
     @Nullable
     private final BatterySaverStateMachine mBatterySaverStateMachine;
@@ -1007,6 +1009,10 @@
             return new InattentiveSleepWarningController();
         }
 
+        FoldGracePeriodProvider createFoldGracePeriodProvider() {
+            return new FoldGracePeriodProvider();
+        }
+
         public SystemPropertiesWrapper createSystemPropertiesWrapper() {
             return new SystemPropertiesWrapper() {
                 @Override
@@ -1147,6 +1153,7 @@
         mHandler = injector.createHandler(mHandlerThread.getLooper(),
                 new PowerManagerHandlerCallback());
         mConstants = new Constants(mHandler);
+        mFoldGracePeriodProvider = injector.createFoldGracePeriodProvider();
         mAmbientDisplayConfiguration = mInjector.createAmbientDisplayConfiguration(context);
         mAmbientDisplaySuppressionController =
                 mInjector.createAmbientDisplaySuppressionController(
@@ -6933,8 +6940,15 @@
                                 + ") doesn't exist");
                     }
                     if ((flags & PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP) != 0) {
-                        if (powerGroup.hasWakeLockKeepingScreenOnLocked()) {
-                            continue;
+                        if (mFoldGracePeriodProvider.isEnabled()) {
+                            if (!powerGroup.hasWakeLockKeepingScreenOnLocked()) {
+                                mNotifier.showDismissibleKeyguard();
+                            }
+                            continue; // never actually goes to sleep for SOFT_SLEEP
+                        } else {
+                            if (powerGroup.hasWakeLockKeepingScreenOnLocked()) {
+                                continue;
+                            }
                         }
                     }
                     if (isNoDoze) {
diff --git a/services/core/java/com/android/server/trust/TEST_MAPPING b/services/core/java/com/android/server/trust/TEST_MAPPING
index fa46acd..0de7c28 100644
--- a/services/core/java/com/android/server/trust/TEST_MAPPING
+++ b/services/core/java/com/android/server/trust/TEST_MAPPING
@@ -12,6 +12,19 @@
         ]
       }
     ],
+    "postsubmit": [
+      {
+        "name": "FrameworksMockingServicesTests",
+        "options": [
+          {
+            "include-filter": "com.android.server.trust"
+          },
+          {
+            "exclude-annotation": "androidx.test.filters.FlakyTest"
+          }
+        ]
+      }
+    ],
     "trust-tablet": [
       {
         "name": "TrustTests",
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 9a85c42..5c603c2 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -74,6 +74,7 @@
 import android.view.WindowManagerGlobal;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.util.DumpUtils;
@@ -1758,6 +1759,11 @@
         }
     };
 
+    @VisibleForTesting
+    void waitForIdle() {
+        mHandler.runWithScissors(() -> {}, 0);
+    }
+
     private boolean isTrustUsuallyManagedInternal(int userId) {
         synchronized (mTrustUsuallyManagedForUser) {
             int i = mTrustUsuallyManagedForUser.indexOfKey(userId);
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 1577cef..2d584c4 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -98,6 +98,8 @@
 import android.view.animation.Interpolator;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.TraceBuffer;
 import com.android.internal.util.function.pooled.PooledLambda;
@@ -297,6 +299,18 @@
         }
     }
 
+    /** It is only used by unit test. */
+    @VisibleForTesting
+    Surface forceShowMagnifierSurface(int displayId) {
+        final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
+        if (displayMagnifier != null) {
+            displayMagnifier.mMagnifedViewport.mWindow.setAlpha(DisplayMagnifier.MagnifiedViewport
+                    .ViewportWindow.AnimationController.MAX_ALPHA);
+            return displayMagnifier.mMagnifedViewport.mWindow.mSurface;
+        }
+        return null;
+    }
+
     void onWindowLayersChanged(int displayId) {
         if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK
                 | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) {
@@ -448,6 +462,7 @@
         }
     }
 
+    // TODO(b/318327737): Remove parameter 't' when removing flag DRAW_IN_WM_LOCK.
     void drawMagnifiedRegionBorderIfNeeded(int displayId, SurfaceControl.Transaction t) {
         if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
             mAccessibilityTracing.logTrace(
@@ -1106,11 +1121,19 @@
             }
 
             void setMagnifiedRegionBorderShown(boolean shown, boolean animate) {
-                if (shown) {
+                if (ViewportWindow.DRAW_IN_WM_LOCK) {
+                    if (shown) {
+                        mFullRedrawNeeded = true;
+                        mOldMagnificationRegion.set(0, 0, 0, 0);
+                    }
+                    mWindow.setShown(shown, animate);
+                    return;
+                }
+                if (mWindow.setShown(shown, animate)) {
                     mFullRedrawNeeded = true;
+                    // Clear the old region, so recomputeBounds will refresh the current region.
                     mOldMagnificationRegion.set(0, 0, 0, 0);
                 }
-                mWindow.setShown(shown, animate);
             }
 
             void getMagnifiedFrameInContentCoords(Rect rect) {
@@ -1130,7 +1153,11 @@
 
             void drawWindowIfNeeded(SurfaceControl.Transaction t) {
                 recomputeBounds();
-                mWindow.drawIfNeeded(t);
+                if (ViewportWindow.DRAW_IN_WM_LOCK) {
+                    mWindow.drawOrRemoveIfNeeded(t);
+                    return;
+                }
+                mWindow.postDrawIfNeeded();
             }
 
             void destroyWindow() {
@@ -1158,23 +1185,28 @@
                 mWindow.dump(pw, prefix);
             }
 
-            private final class ViewportWindow {
+            private final class ViewportWindow implements Runnable {
                 private static final String SURFACE_TITLE = "Magnification Overlay";
+                // TODO(b/318327737): Remove if it is stable.
+                static final boolean DRAW_IN_WM_LOCK = !Flags.drawMagnifierBorderOutsideWmlock();
 
                 private final Region mBounds = new Region();
                 private final Rect mDirtyRect = new Rect();
                 private final Paint mPaint = new Paint();
 
                 private final SurfaceControl mSurfaceControl;
+                /** After initialization, it should only be accessed from animation thread. */
+                private final SurfaceControl.Transaction mTransaction;
                 private final BLASTBufferQueue mBlastBufferQueue;
                 private final Surface mSurface;
 
                 private final AnimationController mAnimationController;
 
                 private boolean mShown;
+                private boolean mLastSurfaceShown;
                 private int mAlpha;
 
-                private boolean mInvalidated;
+                private volatile boolean mInvalidated;
 
                 ViewportWindow(Context context) {
                     SurfaceControl surfaceControl = null;
@@ -1202,6 +1234,7 @@
                     InputMonitor.setTrustedOverlayInputInfo(mSurfaceControl, t,
                             mDisplayContent.getDisplayId(), "Magnification Overlay");
                     t.apply();
+                    mTransaction = t;
                     mSurface = mBlastBufferQueue.createSurface();
 
                     mAnimationController = new AnimationController(context,
@@ -1219,10 +1252,11 @@
                     mInvalidated = true;
                 }
 
-                void setShown(boolean shown, boolean animate) {
+                /** Returns {@code true} if the state is changed to shown. */
+                boolean setShown(boolean shown, boolean animate) {
                     synchronized (mService.mGlobalLock) {
                         if (mShown == shown) {
-                            return;
+                            return false;
                         }
                         mShown = shown;
                         mAnimationController.onFrameShownStateChanged(shown, animate);
@@ -1230,6 +1264,7 @@
                             Slog.i(LOG_TAG, "ViewportWindow shown: " + mShown);
                         }
                     }
+                    return shown;
                 }
 
                 @SuppressWarnings("unused")
@@ -1285,7 +1320,22 @@
                     mService.scheduleAnimationLocked();
                 }
 
-                void drawIfNeeded(SurfaceControl.Transaction t) {
+                void postDrawIfNeeded() {
+                    if (mInvalidated) {
+                        mService.mAnimationHandler.post(this);
+                    }
+                }
+
+                @Override
+                public void run() {
+                    drawOrRemoveIfNeeded(mTransaction);
+                }
+
+                /**
+                 * This method must only be called by animation handler directly to make sure
+                 * thread safe and there is no lock held outside.
+                 */
+                private void drawOrRemoveIfNeeded(SurfaceControl.Transaction t) {
                     // Drawing variables (alpha, dirty rect, and bounds) access is synchronized
                     // using WindowManagerGlobalLock. Grab copies of these values before
                     // drawing on the canvas so that drawing can be performed outside of the lock.
@@ -1293,6 +1343,14 @@
                     Rect drawingRect = null;
                     Region drawingBounds = null;
                     synchronized (mService.mGlobalLock) {
+                        if (!DRAW_IN_WM_LOCK && mBlastBufferQueue.mNativeObject == 0) {
+                            // Complete removal since releaseSurface has been called.
+                            if (mSurface.isValid()) {
+                                mTransaction.remove(mSurfaceControl).apply();
+                                mSurface.release();
+                            }
+                            return;
+                        }
                         if (!mInvalidated) {
                             return;
                         }
@@ -1314,6 +1372,7 @@
                         }
                     }
 
+                    final boolean showSurface;
                     // Draw without holding WindowManagerGlobalLock.
                     if (alpha > 0) {
                         Canvas canvas = null;
@@ -1329,18 +1388,38 @@
                         mPaint.setAlpha(alpha);
                         canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
                         mSurface.unlockCanvasAndPost(canvas);
-                        t.show(mSurfaceControl);
+                        if (DRAW_IN_WM_LOCK) {
+                            t.show(mSurfaceControl);
+                            return;
+                        }
+                        showSurface = true;
                     } else {
-                        t.hide(mSurfaceControl);
+                        if (DRAW_IN_WM_LOCK) {
+                            t.hide(mSurfaceControl);
+                            return;
+                        }
+                        showSurface = false;
+                    }
+
+                    if (showSurface && !mLastSurfaceShown) {
+                        mTransaction.show(mSurfaceControl).apply();
+                        mLastSurfaceShown = true;
+                    } else if (!showSurface && mLastSurfaceShown) {
+                        mTransaction.hide(mSurfaceControl).apply();
+                        mLastSurfaceShown = false;
                     }
                 }
 
+                @GuardedBy("mService.mGlobalLock")
                 void releaseSurface() {
-                    if (mBlastBufferQueue != null) {
-                        mBlastBufferQueue.destroy();
+                    mBlastBufferQueue.destroy();
+                    if (DRAW_IN_WM_LOCK) {
+                        mService.mTransactionFactory.get().remove(mSurfaceControl).apply();
+                        mSurface.release();
+                        return;
                     }
-                    mService.mTransactionFactory.get().remove(mSurfaceControl).apply();
-                    mSurface.release();
+                    // Post to perform cleanup on the thread which handles mSurface.
+                    mService.mAnimationHandler.post(this);
                 }
 
                 void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index febcc05..5fa2610 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2181,7 +2181,8 @@
             // The result receiver is the transition receiver, which will handle the shared element
             // exit transition.
             mHasSceneTransition = options.getAnimationType() == ANIM_SCENE_TRANSITION
-                    && options.getResultReceiver() != null;
+                    && options.getSceneTransitionInfo() != null
+                    && options.getSceneTransitionInfo().getResultReceiver() != null;
             final PendingIntent usageReport = options.getUsageTimeReport();
             if (usageReport != null) {
                 appTimeTracker = new AppTimeTracker(usageReport);
@@ -5178,16 +5179,15 @@
         return mPendingOptions;
     }
 
-    ActivityOptions takeOptions() {
-        if (DEBUG_TRANSITION) Slog.i(TAG, "Taking options for " + this + " callers="
-                + Debug.getCallers(6));
+    ActivityOptions.SceneTransitionInfo takeSceneTransitionInfo() {
+        if (DEBUG_TRANSITION) {
+            Slog.i(TAG, "Taking SceneTransitionInfo for " + this + " callers="
+                    + Debug.getCallers(6));
+        }
         if (mPendingOptions == null) return null;
         final ActivityOptions opts = mPendingOptions;
         mPendingOptions = null;
-        // Strip sensitive information from options before sending it to app.
-        opts.setRemoteTransition(null);
-        opts.setRemoteAnimationAdapter(null);
-        return opts;
+        return opts.getSceneTransitionInfo();
     }
 
     RemoteTransition takeRemoteTransition() {
@@ -6153,7 +6153,7 @@
 
             try {
                 mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
-                        StartActivityItem.obtain(token, takeOptions()));
+                        StartActivityItem.obtain(token, takeSceneTransitionInfo()));
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending start: " + intent.getComponent(), e);
             }
@@ -6258,8 +6258,11 @@
 
     void handleAlreadyVisible() {
         try {
-            if (returningOptions != null) {
-                app.getThread().scheduleOnNewActivityOptions(token, returningOptions.toBundle());
+            if (returningOptions != null
+                    && returningOptions.getAnimationType() == ANIM_SCENE_TRANSITION
+                    && returningOptions.getSceneTransitionInfo() != null) {
+                app.getThread().scheduleOnNewSceneTransitionInfo(token,
+                        returningOptions.getSceneTransitionInfo());
             }
         } catch(RemoteException e) {
         }
@@ -6608,10 +6611,6 @@
         return hasProcess() && !app.isCrashing() && !app.isNotResponding();
     }
 
-    void startFreezingScreenLocked(int configChanges) {
-        startFreezingScreenLocked(app, configChanges);
-    }
-
     void startFreezingScreenLocked(WindowProcessController app, int configChanges) {
         if (mayFreezeScreenLocked(app)) {
             if (getParent() == null) {
@@ -8095,12 +8094,8 @@
      * Set the last reported configuration to the client. Should be called whenever
      * a new merged configuration is sent to the client for this activity.
      */
-    void setLastReportedConfiguration(@NonNull MergedConfiguration config) {
-        setLastReportedConfiguration(config.getGlobalConfiguration(),
-            config.getOverrideConfiguration());
-    }
-
-    private void setLastReportedConfiguration(Configuration global, Configuration override) {
+    void setLastReportedConfiguration(@NonNull Configuration global,
+            @NonNull Configuration override) {
         mLastReportedConfiguration.setConfiguration(global, override);
     }
 
@@ -9634,7 +9629,7 @@
             configChangeFlags |= changes;
             if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating
                     && !mTransitionController.isShellTransitionsEnabled()) {
-                startFreezingScreenLocked(mAtmService.mTmpUpdateConfigurationResult.changes);
+                startFreezingScreenLocked(app, mAtmService.mTmpUpdateConfigurationResult.changes);
             }
             final boolean displayMayChange = mTmpConfig.windowConfiguration.getDisplayRotation()
                     != getWindowConfiguration().getDisplayRotation()
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 1872f6e..ec0e3e7 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -134,7 +134,6 @@
 import android.os.WorkSource;
 import android.provider.MediaStore;
 import android.util.ArrayMap;
-import android.util.MergedConfiguration;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -820,8 +819,6 @@
         proc.pauseConfigurationDispatch();
 
         try {
-            r.startFreezingScreenLocked(proc, 0);
-
             // schedule launch ticks to collect information about slow apps.
             r.startLaunchTickingLocked();
             r.lastLaunchTime = SystemClock.uptimeMillis();
@@ -908,13 +905,9 @@
                         r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY);
                 mService.getAppWarningsLocked().onStartActivity(r);
 
-                // Because we could be starting an Activity in the system process this may not go
-                // across a Binder interface which would create a new Configuration. Consequently
-                // we have to always create a new Configuration here.
                 final Configuration procConfig = proc.prepareConfigurationForLaunchingActivity();
-                final MergedConfiguration mergedConfiguration = new MergedConfiguration(
-                        procConfig, r.getMergedOverrideConfiguration());
-                r.setLastReportedConfiguration(mergedConfiguration);
+                final Configuration overrideConfig = r.getMergedOverrideConfiguration();
+                r.setLastReportedConfiguration(procConfig, overrideConfig);
 
                 logIfTransactionTooLarge(r.intent, r.getSavedState());
 
@@ -932,13 +925,10 @@
                 final int deviceId = getDeviceIdForDisplayId(r.getDisplayId());
                 final LaunchActivityItem launchActivityItem = LaunchActivityItem.obtain(r.token,
                         r.intent, System.identityHashCode(r), r.info,
-                        // TODO: Have this take the merged configuration instead of separate global
-                        // and override configs.
-                        mergedConfiguration.getGlobalConfiguration(),
-                        mergedConfiguration.getOverrideConfiguration(), deviceId,
+                        procConfig, overrideConfig, deviceId,
                         r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,
                         proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
-                        results, newIntents, r.takeOptions(), isTransitionForward,
+                        results, newIntents, r.takeSceneTransitionInfo(), isTransitionForward,
                         proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
                         r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken);
 
diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
index 975fdc0..0f9e5b0 100644
--- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
+++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java
@@ -179,6 +179,12 @@
             if (physicalDisplayUpdated) {
                 onDisplayUpdated(transition, fromRotation, startBounds);
             } else {
+                final TransitionRequestInfo.DisplayChange displayChange =
+                        getCurrentDisplayChange(fromRotation, startBounds);
+                mDisplayContent.mTransitionController.requestStartTransition(transition,
+                        /* startTask= */ null, /* remoteTransition= */ null, displayChange);
+                mDisplayContent.mTransitionController.setDisplaySyncMethod(displayChange,
+                        mDisplayContent);
                 transition.setAllReady();
             }
         });
@@ -204,15 +210,8 @@
         return new DisplayInfo(mNonOverrideDisplayInfo);
     }
 
-    /**
-     * Called when physical display is updated, this could happen e.g. on foldable
-     * devices when the physical underlying display is replaced. This method should be called
-     * when the new display info is already applied to the WM hierarchy.
-     *
-     * @param fromRotation rotation before the display change
-     * @param startBounds  display bounds before the display change
-     */
-    private void onDisplayUpdated(@NonNull Transition transition, int fromRotation,
+    @NonNull
+    private TransitionRequestInfo.DisplayChange getCurrentDisplayChange(int fromRotation,
             @NonNull Rect startBounds) {
         final Rect endBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth,
                 mDisplayContent.mInitialDisplayHeight);
@@ -224,6 +223,23 @@
         displayChange.setEndAbsBounds(endBounds);
         displayChange.setStartRotation(fromRotation);
         displayChange.setEndRotation(toRotation);
+        return displayChange;
+    }
+
+    /**
+     * Called when physical display is updated, this could happen e.g. on foldable
+     * devices when the physical underlying display is replaced. This method should be called
+     * when the new display info is already applied to the WM hierarchy.
+     *
+     * @param fromRotation rotation before the display change
+     * @param startBounds  display bounds before the display change
+     */
+    private void onDisplayUpdated(@NonNull Transition transition, int fromRotation,
+            @NonNull Rect startBounds) {
+        final int toRotation = mDisplayContent.getRotation();
+
+        final TransitionRequestInfo.DisplayChange displayChange =
+                getCurrentDisplayChange(fromRotation, startBounds);
         displayChange.setPhysicalDisplayChanged(true);
 
         mDisplayContent.mTransitionController.requestStartTransition(transition,
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 460a68f..63ca592 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -40,6 +40,7 @@
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
@@ -959,15 +960,17 @@
 
             case TYPE_BASE_APPLICATION:
 
-                // A non-translucent main app window isn't allowed to fit insets, as it would create
-                // a hole on the display!
+                // A non-translucent main app window isn't allowed to fit insets or display cutouts,
+                // as it would create a hole on the display!
                 if (attrs.isFullscreen() && win.mActivityRecord != null
                         && win.mActivityRecord.fillsParent()
-                        && (win.mAttrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0
-                        && attrs.getFitInsetsTypes() != 0) {
+                        && (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS) != 0
+                        && (attrs.getFitInsetsTypes() != 0
+                                || (attrs.privateFlags & PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED) != 0
+                                        && attrs.layoutInDisplayCutoutMode
+                                                != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS)) {
                     throw new IllegalArgumentException("Illegal attributes: Main activity window"
-                            + " that isn't translucent trying to fit insets: "
-                            + attrs.getFitInsetsTypes()
+                            + " that isn't translucent trying to fit insets or display cutouts."
                             + " attrs=" + attrs);
                 }
                 break;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index c3aca6f..708d63e 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -634,8 +634,8 @@
     }
 
     /** Sets the sync method for the display change. */
-    private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
-            @NonNull Transition displayTransition, @NonNull DisplayContent displayContent) {
+    void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
+            @NonNull DisplayContent displayContent) {
         final Rect startBounds = displayChange.getStartAbsBounds();
         final Rect endBounds = displayChange.getEndAbsBounds();
         if (startBounds == null || endBounds == null) return;
@@ -686,7 +686,7 @@
                     trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
             if (newTransition != null && displayChange != null && trigger != null
                     && trigger.asDisplayContent() != null) {
-                setDisplaySyncMethod(displayChange, newTransition, trigger.asDisplayContent());
+                setDisplaySyncMethod(displayChange, trigger.asDisplayContent());
             }
         }
         if (trigger != null) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index e28262d..bdea1bc 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -3938,7 +3938,13 @@
     @Nullable
     BLASTSyncEngine.SyncGroup getSyncGroup() {
         if (mSyncGroup != null) return mSyncGroup;
-        if (mParent != null) return mParent.getSyncGroup();
+        WindowContainer<?> parent = mParent;
+        while (parent != null) {
+            if (parent.mSyncGroup != null) {
+                return parent.mSyncGroup;
+            }
+            parent = parent.mParent;
+        }
         return null;
     }
 
@@ -3972,7 +3978,7 @@
      * @param cancel If true, this is being finished because it is leaving the sync group rather
      *               than due to the sync group completing.
      */
-    void finishSync(Transaction outMergedTransaction, BLASTSyncEngine.SyncGroup group,
+    void finishSync(Transaction outMergedTransaction, @Nullable BLASTSyncEngine.SyncGroup group,
             boolean cancel) {
         if (mSyncState == SYNC_STATE_NONE) return;
         final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup();
@@ -4000,7 +4006,8 @@
         if (!isVisibleRequested()) {
             return true;
         }
-        if (mSyncState == SYNC_STATE_NONE) {
+        if (mSyncState == SYNC_STATE_NONE && getSyncGroup() != null) {
+            Slog.i(TAG, "prepareSync in isSyncFinished: " + this);
             prepareSync();
         }
         if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW) {
@@ -4059,16 +4066,18 @@
             }
             if (newParent == null) {
                 // This is getting removed.
+                final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup();
                 if (oldParent.mSyncState != SYNC_STATE_NONE) {
                     // In order to keep the transaction in sync, merge it into the parent.
-                    finishSync(oldParent.mSyncTransaction, getSyncGroup(), true /* cancel */);
-                } else if (mSyncGroup != null) {
-                    // This is watched directly by the sync-group, so merge this transaction into
-                    // into the sync-group so it isn't lost
-                    finishSync(mSyncGroup.getOrphanTransaction(), mSyncGroup, true /* cancel */);
+                    finishSync(oldParent.mSyncTransaction, syncGroup, true /* cancel */);
+                } else if (syncGroup != null) {
+                    // This is watched by the sync-group, so merge this transaction into the
+                    // sync-group for not losing the operations in the transaction.
+                    finishSync(syncGroup.getOrphanTransaction(), syncGroup, true /* cancel */);
                 } else {
-                    throw new IllegalStateException("This container is in sync mode without a sync"
-                            + " group: " + this);
+                    Slog.wtf(TAG, this + " is in sync mode without a sync group");
+                    // Make sure the removal transaction take effect.
+                    finishSync(getPendingTransaction(), null /* group */, true /* cancel */);
                 }
                 return;
             } else if (mSyncGroup == null) {
diff --git a/services/core/xsd/display-layout-config/display-layout-config.xsd b/services/core/xsd/display-layout-config/display-layout-config.xsd
index 4e95465..73c13ce 100644
--- a/services/core/xsd/display-layout-config/display-layout-config.xsd
+++ b/services/core/xsd/display-layout-config/display-layout-config.xsd
@@ -49,7 +49,7 @@
 
     <xs:complexType name="display">
         <xs:sequence>
-            <xs:element name="address" type="xs:nonNegativeInteger"/>
+            <xs:group ref="displayReference" minOccurs="1" maxOccurs="1"/>
             <xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" />
             <xs:element name="brightnessThrottlingMapId" type="xs:string" minOccurs="0" maxOccurs="1" />
             <xs:element name="powerThrottlingMapId" type="xs:string" minOccurs="0" maxOccurs="1" />
@@ -67,4 +67,11 @@
             </xs:simpleType>
         </xs:attribute>
     </xs:complexType>
+
+    <xs:group name="displayReference">
+        <xs:choice>
+            <xs:element name="address" type="xs:nonNegativeInteger" minOccurs="0"/>
+            <xs:element name="port" type="xs:nonNegativeInteger" minOccurs="0"/>
+        </xs:choice>
+    </xs:group>
 </xs:schema>
diff --git a/services/core/xsd/display-layout-config/schema/current.txt b/services/core/xsd/display-layout-config/schema/current.txt
index 195cae5..0d2f6a8 100644
--- a/services/core/xsd/display-layout-config/schema/current.txt
+++ b/services/core/xsd/display-layout-config/schema/current.txt
@@ -3,22 +3,24 @@
 
   public class Display {
     ctor public Display();
-    method public java.math.BigInteger getAddress();
+    method public java.math.BigInteger getAddress_optional();
     method public String getBrightnessThrottlingMapId();
     method public String getDisplayGroup();
     method public java.math.BigInteger getLeadDisplayAddress();
+    method public java.math.BigInteger getPort_optional();
     method public String getPosition();
     method public String getPowerThrottlingMapId();
     method public String getRefreshRateThermalThrottlingMapId();
     method public String getRefreshRateZoneId();
     method public boolean isDefaultDisplay();
     method public boolean isEnabled();
-    method public void setAddress(java.math.BigInteger);
+    method public void setAddress_optional(java.math.BigInteger);
     method public void setBrightnessThrottlingMapId(String);
     method public void setDefaultDisplay(boolean);
     method public void setDisplayGroup(String);
     method public void setEnabled(boolean);
     method public void setLeadDisplayAddress(java.math.BigInteger);
+    method public void setPort_optional(java.math.BigInteger);
     method public void setPosition(String);
     method public void setPowerThrottlingMapId(String);
     method public void setRefreshRateThermalThrottlingMapId(String);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index e0232b1..14dc0eb 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -32,6 +32,7 @@
 import android.annotation.Nullable;
 import android.app.AppGlobals;
 import android.app.BroadcastOptions;
+import android.app.admin.BooleanPolicyValue;
 import android.app.admin.DevicePolicyIdentifiers;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyState;
@@ -142,6 +143,67 @@
         mAdminPolicySize = new SparseArray<>();
     }
 
+    private void maybeForceEnforcementRefreshLocked(@NonNull PolicyDefinition<?> policyDefinition) {
+        try {
+            if (shouldForceEnforcementRefresh(policyDefinition)) {
+                // This is okay because it's only true for user restrictions which are all <Boolean>
+                forceEnforcementRefreshLocked((PolicyDefinition<Boolean>) policyDefinition);
+            }
+        } catch (Throwable e) {
+            // Catch any possible exceptions just to be on the safe side
+            Log.e(TAG, "Exception throw during maybeForceEnforcementRefreshLocked", e);
+        }
+    }
+
+    private boolean shouldForceEnforcementRefresh(@NonNull PolicyDefinition<?> policyDefinition) {
+        // These are all "not nullable" but for the purposes of maximum safety for a lightly tested
+        // change we check here
+        if (policyDefinition == null) {
+            return false;
+        }
+        PolicyKey policyKey = policyDefinition.getPolicyKey();
+        if (policyKey == null) {
+            return false;
+        }
+
+        if (policyKey instanceof UserRestrictionPolicyKey) {
+            // b/307481299 We must force all user restrictions to re-sync local
+            // + global on each set/clear
+            return true;
+        }
+
+        return false;
+    }
+
+    private void forceEnforcementRefreshLocked(PolicyDefinition<Boolean> policyDefinition) {
+        Binder.withCleanCallingIdentity(() -> {
+            // Sync global state
+            PolicyValue<Boolean> globalValue = new BooleanPolicyValue(false);
+            try {
+                PolicyState<Boolean> policyState = getGlobalPolicyStateLocked(policyDefinition);
+                globalValue = policyState.getCurrentResolvedPolicy();
+            } catch (IllegalArgumentException e) {
+                // Expected for local-only policies
+            }
+
+            enforcePolicy(policyDefinition, globalValue, UserHandle.USER_ALL);
+
+            // Loop through each user and sync that user's state
+            for (UserInfo user : mUserManager.getUsers()) {
+                PolicyValue<Boolean> localValue = new BooleanPolicyValue(false);
+                try {
+                    PolicyState<Boolean> localPolicyState = getLocalPolicyStateLocked(
+                            policyDefinition, user.id);
+                    localValue = localPolicyState.getCurrentResolvedPolicy();
+                } catch (IllegalArgumentException e) {
+                    // Expected for global-only policies
+                }
+
+                enforcePolicy(policyDefinition, localValue, user.id);
+            }
+        });
+    }
+
     /**
      * Set the policy for the provided {@code policyDefinition} (see {@link PolicyDefinition}) and
      * {@code enforcingAdmin} to the provided {@code value}.
@@ -188,6 +250,7 @@
             // No need to notify admins as no new policy is actually enforced, we're just filling in
             // the data structures.
             if (!skipEnforcePolicy) {
+                maybeForceEnforcementRefreshLocked(policyDefinition);
                 if (policyChanged) {
                     onLocalPolicyChangedLocked(policyDefinition, enforcingAdmin, userId);
                 }
@@ -278,6 +341,7 @@
         Objects.requireNonNull(enforcingAdmin);
 
         synchronized (mLock) {
+            maybeForceEnforcementRefreshLocked(policyDefinition);
             if (!hasLocalPolicyLocked(policyDefinition, userId)) {
                 return;
             }
@@ -451,6 +515,7 @@
             // No need to notify admins as no new policy is actually enforced, we're just filling in
             // the data structures.
             if (!skipEnforcePolicy) {
+                maybeForceEnforcementRefreshLocked(policyDefinition);
                 if (policyChanged) {
                     onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
                 }
@@ -506,6 +571,7 @@
 
             boolean policyChanged = policyState.removePolicy(enforcingAdmin);
 
+            maybeForceEnforcementRefreshLocked(policyDefinition);
             if (policyChanged) {
                 onGlobalPolicyChangedLocked(policyDefinition, enforcingAdmin);
             }
diff --git a/services/robotests/Android.bp b/services/robotests/Android.bp
index 52eae21..a70802a 100644
--- a/services/robotests/Android.bp
+++ b/services/robotests/Android.bp
@@ -57,9 +57,13 @@
     ],
     static_libs: [
         "androidx.test.ext.truth",
+        "Settings-robo-testutils",
+        "SettingsLib-robo-testutils",
     ],
 
     instrumentation_for: "FrameworksServicesLib",
+
+    upstream: true,
 }
 
 filegroup {
diff --git a/services/robotests/backup/Android.bp b/services/robotests/backup/Android.bp
index 8b9efb3..569786b 100644
--- a/services/robotests/backup/Android.bp
+++ b/services/robotests/backup/Android.bp
@@ -57,6 +57,8 @@
     // Include the testing libraries
     libs: [
         "mockito-robolectric-prebuilt",
+        "Settings-robo-testutils",
+        "SettingsLib-robo-testutils",
         "platform-test-annotations",
         "testng",
         "truth",
@@ -64,4 +66,6 @@
 
     instrumentation_for: "BackupFrameworksServicesLib",
 
+    upstream: true,
+
 }
diff --git a/services/robotests/backup/config/robolectric.properties b/services/robotests/backup/config/robolectric.properties
index 850557a..1ebf6d4 100644
--- a/services/robotests/backup/config/robolectric.properties
+++ b/services/robotests/backup/config/robolectric.properties
@@ -1 +1,3 @@
-sdk=NEWEST_SDK
\ No newline at end of file
+sdk=NEWEST_SDK
+looperMode=LEGACY
+shadows=com.android.server.testing.shadows.FrameworkShadowLooper
diff --git a/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java b/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
index ee5a534..6839a06 100644
--- a/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/fullbackup/AppMetadataBackupWriterTest.java
@@ -57,6 +57,7 @@
             ShadowBackupDataOutput.class,
             ShadowEnvironment.class,
             ShadowFullBackup.class,
+            ShadowSigningInfo.class,
         })
 public class AppMetadataBackupWriterTest {
     private static final String TEST_PACKAGE = "com.test.package";
diff --git a/services/robotests/backup/src/com/android/server/backup/fullbackup/ShadowSigningInfo.java b/services/robotests/backup/src/com/android/server/backup/fullbackup/ShadowSigningInfo.java
new file mode 100644
index 0000000..53d807c
--- /dev/null
+++ b/services/robotests/backup/src/com/android/server/backup/fullbackup/ShadowSigningInfo.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 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.backup.fullbackup;
+
+import static android.os.Build.VERSION_CODES.P;
+
+import android.content.pm.SigningInfo;
+
+import org.robolectric.annotation.Implements;
+
+@Implements(value = SigningInfo.class, minSdk = P)
+public class ShadowSigningInfo {
+}
diff --git a/services/robotests/src/com/android/server/location/gnss/NtpNetworkTimeHelperTest.java b/services/robotests/src/com/android/server/location/gnss/NtpNetworkTimeHelperTest.java
index 4949091..0092763 100644
--- a/services/robotests/src/com/android/server/location/gnss/NtpNetworkTimeHelperTest.java
+++ b/services/robotests/src/com/android/server/location/gnss/NtpNetworkTimeHelperTest.java
@@ -35,6 +35,7 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
 import org.robolectric.shadows.ShadowLooper;
 
 import java.util.concurrent.CountDownLatch;
@@ -45,6 +46,7 @@
  */
 @RunWith(RobolectricTestRunner.class)
 @Presubmit
+@LooperMode(LooperMode.Mode.LEGACY)
 public class NtpNetworkTimeHelperTest {
 
     private static final long MOCK_NTP_TIME = 1519930775453L;
diff --git a/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java
index 16d16cd..3681bd4 100644
--- a/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java
+++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowLooper.java
@@ -21,12 +21,15 @@
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.RealObject;
+import org.robolectric.shadows.LooperShadowPicker;
+import org.robolectric.shadows.ShadowLegacyLooper;
 import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.shadows.ShadowPausedLooper;
 
 import java.util.Optional;
 
-@Implements(value = Looper.class)
-public class FrameworkShadowLooper extends ShadowLooper {
+@Implements(value = Looper.class, shadowPicker = FrameworkShadowLooper.Picker.class)
+public class FrameworkShadowLooper extends ShadowLegacyLooper {
     @RealObject private Looper mLooper;
     private Optional<Boolean> mIsCurrentThread = Optional.empty();
 
@@ -45,4 +48,10 @@
         }
         return Thread.currentThread() == mLooper.getThread();
     }
+
+    public static class Picker extends LooperShadowPicker<ShadowLooper> {
+        public Picker() {
+            super(FrameworkShadowLooper.class, ShadowPausedLooper.class);
+        }
+    }
 }
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
index 4a99486..1da6759 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowApplicationPackageManager.java
@@ -95,7 +95,6 @@
         sPackageAppEnabledStates.put(packageName, Integer.valueOf(newState));  // flags unused here.
     }
 
-    @Override
     protected PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
             throws NameNotFoundException {
         if (!sPackageInfos.containsKey(packageName)) {
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index ffe6dc5..56423b9 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -40,6 +40,7 @@
         "frameworks-base-testutils",
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
+        "ravenwood-junit",
         "services.core",
         "service-permission.stubs.system_server",
         "servicestests-core-utils",
@@ -66,6 +67,28 @@
     },
 }
 
+android_ravenwood_test {
+    name: "FrameworksInputMethodSystemServerTests_host",
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.test.rules",
+        "framework",
+        "mockito_ravenwood",
+        "ravenwood-runtime",
+        "ravenwood-utils",
+        "services",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    srcs: [
+        "src/com/android/server/inputmethod/**/ClientControllerTest.java",
+    ],
+    sdk_version: "test_current",
+    auto_gen_config: true,
+}
+
 android_test {
     name: "FrameworksImeTests",
     defaults: [
@@ -88,6 +111,7 @@
         "frameworks-base-testutils",
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
+        "ravenwood-junit",
         "services.core",
         "service-permission.stubs.system_server",
         "servicestests-core-utils",
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
new file mode 100644
index 0000000..3c8f5c9
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.PackageManagerInternal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+import android.view.Display;
+import android.view.inputmethod.InputBinding;
+
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+// This test is designed to run on both device and host (Ravenwood) side.
+public final class ClientControllerTest {
+    private static final int ANY_DISPLAY_ID = Display.DEFAULT_DISPLAY;
+    private static final int ANY_CALLER_UID = 1;
+    private static final int ANY_CALLER_PID = 1;
+
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true).build();
+
+    @Mock
+    private PackageManagerInternal mMockPackageManagerInternal;
+
+    @Mock(extraInterfaces = IBinder.class)
+    private IInputMethodClient mClient;
+
+    @Mock
+    private IRemoteInputConnection mConnection;
+
+    @Mock
+    private IBinder.DeathRecipient mDeathRecipient;
+
+    private Handler mHandler;
+
+    private ClientController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mHandler = new Handler(Looper.getMainLooper());
+        mController = new ClientController(mMockPackageManagerInternal);
+        when(mClient.asBinder()).thenReturn((IBinder) mClient);
+    }
+
+    @Test
+    // TODO(b/314150112): Enable host side mode for this test once b/315544364 is fixed.
+    @IgnoreUnderRavenwood(blockedBy = {InputBinding.class, IInputMethodClientInvoker.class})
+    public void testAddClient_cannotAddTheSameClientTwice() {
+        var invoker = IInputMethodClientInvoker.create(mClient, mHandler);
+
+        synchronized (ImfLock.class) {
+            mController.addClient(invoker, mConnection, ANY_DISPLAY_ID, mDeathRecipient,
+                    ANY_CALLER_UID, ANY_CALLER_PID);
+
+            SecurityException thrown = assertThrows(SecurityException.class,
+                    () -> {
+                        synchronized (ImfLock.class) {
+                            mController.addClient(invoker, mConnection, ANY_DISPLAY_ID,
+                                    mDeathRecipient, ANY_CALLER_UID, ANY_CALLER_PID);
+                        }
+                    });
+            assertThat(thrown.getMessage()).isEqualTo(
+                    "uid=1/pid=1/displayId=0 is already registered");
+        }
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index 3199e06..438bea4 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -22,6 +22,7 @@
 import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
 import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SWITCH_USER;
 import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
+import static com.android.server.inputmethod.ClientController.ClientState;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
@@ -68,8 +69,7 @@
         super.setUp();
         mVisibilityApplier =
                 (DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier();
-        mInputMethodManagerService.setAttachedClientForTesting(
-                mock(InputMethodManagerService.ClientState.class));
+        mInputMethodManagerService.setAttachedClientForTesting(mock(ClientState.class));
     }
 
     @Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
index 8cc3408..567792e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java
@@ -25,6 +25,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.layout.DisplayIdProducer;
 import com.android.server.display.layout.Layout;
 
@@ -45,6 +46,7 @@
     private DeviceStateToLayoutMap mDeviceStateToLayoutMap;
 
     @Mock DisplayIdProducer mDisplayIdProducerMock;
+    @Mock DisplayManagerFlags mMockFlags;
 
     @Before
     public void setUp() throws IOException {
@@ -52,7 +54,7 @@
 
         Mockito.when(mDisplayIdProducerMock.getId(false)).thenReturn(1);
 
-        setupDeviceStateToLayoutMap();
+        setupDeviceStateToLayoutMap(getContent());
     }
 
     //////////////////
@@ -268,6 +270,41 @@
                 IllegalArgumentException.class, () -> layout.postProcessLocked());
     }
 
+    @Test
+    public void testPortInLayout_disabledFlag() {
+        Mockito.when(mMockFlags.isPortInDisplayLayoutEnabled()).thenReturn(false);
+        assertThrows("Expected IllegalArgumentException when using <port>",
+                IllegalArgumentException.class,
+                () -> setupDeviceStateToLayoutMap(getPortContent()));
+    }
+
+    @Test
+    public void testPortInLayout_readLayout() throws Exception {
+        Mockito.when(mMockFlags.isPortInDisplayLayoutEnabled()).thenReturn(true);
+        setupDeviceStateToLayoutMap(getPortContent());
+
+        Layout configLayout = mDeviceStateToLayoutMap.get(0);
+
+        Layout testLayout = new Layout();
+        testLayout.createDisplayLocked(DisplayAddress.fromPortAndModel(123, null),
+                /* isDefault= */ true, /* isEnabled= */ true, /* displayGroupName= */ null,
+                mDisplayIdProducerMock,  Layout.Display.POSITION_UNKNOWN,
+                /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null,
+                /* refreshRateZoneId= */ null,
+                /* refreshRateThermalThrottlingMapId= */ null,
+                /* powerThrottlingMapId= */ null);
+        testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(78910L),
+                /* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ null,
+                mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN,
+                /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null,
+                /* refreshRateZoneId= */ null,
+                /* refreshRateThermalThrottlingMapId= */ null,
+                /* powerThrottlingMapId= */ null);
+        testLayout.postProcessLocked();
+
+        assertEquals(testLayout, configLayout);
+    }
+
     ////////////////////
     // Helper Methods //
     ////////////////////
@@ -287,13 +324,28 @@
                 /* powerThrottlingMapId= */ null);
     }
 
-    private void setupDeviceStateToLayoutMap() throws IOException {
+    private void setupDeviceStateToLayoutMap(String content) throws IOException {
         Path tempFile = Files.createTempFile("device_state_layout_map", ".tmp");
-        Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
-        mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(mDisplayIdProducerMock,
+        Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8));
+        mDeviceStateToLayoutMap = new DeviceStateToLayoutMap(mDisplayIdProducerMock, mMockFlags,
                 tempFile.toFile());
     }
 
+    private String getPortContent() {
+        return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                +  "<layouts>\n"
+                +    "<layout>\n"
+                +      "<state>0</state> \n"
+                +      "<display enabled=\"true\" defaultDisplay=\"true\">\n"
+                +        "<port>123</port>\n"
+                +      "</display>\n"
+                +      "<display enabled=\"false\">\n"
+                +        "<address>78910</address>\n"
+                +      "</display>\n"
+                +    "</layout>\n"
+                +  "</layouts>\n";
+    }
+
     private String getContent() {
         return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                 +  "<layouts>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 28ec896..bed6f92 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -49,8 +49,9 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -83,7 +84,6 @@
 import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
 
 import java.io.File;
 import java.io.InputStream;
@@ -111,14 +111,14 @@
     private final DisplayIdProducer mIdProducer = (isDefault) ->
             isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++;
 
+    private DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy;
+
     @Mock LogicalDisplayMapper.Listener mListenerMock;
     @Mock Context mContextMock;
     @Mock FoldSettingProvider mFoldSettingProviderMock;
     @Mock Resources mResourcesMock;
     @Mock IPowerManager mIPowerManagerMock;
     @Mock IThermalService mIThermalServiceMock;
-    @Spy DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy =
-            new DeviceStateToLayoutMap(mIdProducer, NON_EXISTING_FILE);
     @Mock DisplayManagerFlags mFlagsMock;
     @Mock DisplayAdapter mDisplayAdapterMock;
 
@@ -131,6 +131,8 @@
         System.setProperty("dexmaker.share_classloader", "true");
         MockitoAnnotations.initMocks(this);
 
+        mDeviceStateToLayoutMapSpy =
+                spy(new DeviceStateToLayoutMap(mIdProducer, mFlagsMock, NON_EXISTING_FILE));
         mDisplayDeviceRepo = new DisplayDeviceRepository(
                 new DisplayManagerService.SyncRoot(),
                 new PersistentDataStore(new PersistentDataStore.Injector() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 60a0c03..a0e5fd8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -3434,6 +3434,11 @@
             return mSensorManagerInternal;
         }
 
+        @Override
+        public VotesStatsReporter getVotesStatsReporter(boolean refreshRateVotingTelemetryEnabled) {
+            return null;
+        }
+
         protected Display createDisplay(int id) {
             return new Display(DisplayManagerGlobal.getInstance(), id, mDisplayInfo,
                     ApplicationProvider.getApplicationContext().getResources());
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
index f677401..2815fcb 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java
@@ -58,7 +58,7 @@
     private RegisteringFakesInjector mInjector = new RegisteringFakesInjector();
 
     private final TestHandler mHandler = new TestHandler(null);
-    private final VotesStorage mStorage = new VotesStorage(() -> {});
+    private final VotesStorage mStorage = new VotesStorage(() -> {}, null);
 
     @Before
     public void setUp() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
index 50e2392..1f6f1a4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java
@@ -51,7 +51,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mVotesStorage = new VotesStorage(mVotesListener);
+        mVotesStorage = new VotesStorage(mVotesListener, null);
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 113511e..321d945 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -73,6 +73,7 @@
         // TODO: remove once Android migrates to JUnit 4.12, which provides assertThrows
         "testng",
         "compatibility-device-util-axt",
+        "flag-junit",
     ],
 
     libs: [
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
index 57326b2..d08cdc7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -343,10 +343,12 @@
         List<ApexManager.ScanResult> scanResults = scanApexInfos(apexInfo);
         mApexManager.notifyScanResult(scanResults);
 
-        assertThat(mApexManager.getApkInApexInstallError(activeApex.apexModuleName)).isNull();
+        final String apexPackageName = mApexManager.getActivePackageNameForApexModuleName(
+                activeApex.apexModuleName);
+        assertThat(mApexManager.getApkInApexInstallError(apexPackageName)).isNull();
         mApexManager.reportErrorWithApkInApex(activeApex.apexDirectory.getAbsolutePath(),
                 "Some random error");
-        assertThat(mApexManager.getApkInApexInstallError(activeApex.apexModuleName))
+        assertThat(mApexManager.getApkInApexInstallError(apexPackageName))
                 .isEqualTo("Some random error");
     }
 
@@ -370,9 +372,11 @@
         List<ApexManager.ScanResult> scanResults = scanApexInfos(apexInfo);
         mApexManager.notifyScanResult(scanResults);
 
-        assertThat(mApexManager.getApksInApex(activeApex.apexModuleName)).isEmpty();
+        final String apexPackageName = mApexManager.getActivePackageNameForApexModuleName(
+                activeApex.apexModuleName);
+        assertThat(mApexManager.getApksInApex(apexPackageName)).isEmpty();
         mApexManager.registerApkInApex(fakeApkInApex);
-        assertThat(mApexManager.getApksInApex(activeApex.apexModuleName)).isEmpty();
+        assertThat(mApexManager.getApksInApex(apexPackageName)).isEmpty();
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 9851bc1..c42c735 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -16,24 +16,23 @@
 
 package com.android.server.trust;
 
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.argThat;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.anyBoolean;
-
 import android.Manifest;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManager;
 import android.app.trust.ITrustListener;
 import android.app.trust.ITrustManager;
 import android.content.BroadcastReceiver;
@@ -45,13 +44,15 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
 import android.net.Uri;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
-import android.os.test.TestLooper;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.service.trust.TrustAgentService;
 import android.testing.TestableContext;
@@ -61,12 +62,11 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 
-import com.google.android.collect.Lists;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -74,37 +74,60 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
-import org.mockito.MockitoSession;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
 
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Random;
+import java.util.Collection;
+import java.util.List;
 
 public class TrustManagerServiceTest {
 
     @Rule
-    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+            .mockStatic(ServiceManager.class)
+            .mockStatic(WindowManagerGlobal.class)
+            .build();
+
     @Rule
     public final MockContext mMockContext = new MockContext(
             ApplicationProvider.getApplicationContext());
 
     private static final String URI_SCHEME_PACKAGE = "package";
-    private static final int TEST_USER_ID = UserHandle.USER_SYSTEM;
+    private static final int TEST_USER_ID = 50;
 
-    private final TestLooper mLooper = new TestLooper();
     private final ArrayList<ResolveInfo> mTrustAgentResolveInfoList = new ArrayList<>();
-    private final LockPatternUtils mLockPatternUtils = new LockPatternUtils(mMockContext);
-    private final TrustManagerService mService = new TrustManagerService(mMockContext);
+    private final ArrayList<ComponentName> mKnownTrustAgents = new ArrayList<>();
+    private final ArrayList<ComponentName> mEnabledTrustAgents = new ArrayList<>();
 
-    @Mock
-    private PackageManager mPackageManagerMock;
+    private @Mock ActivityManager mActivityManager;
+    private @Mock DevicePolicyManager mDevicePolicyManager;
+    private @Mock LockPatternUtils mLockPatternUtils;
+    private @Mock PackageManager mPackageManager;
+    private @Mock UserManager mUserManager;
+    private @Mock IWindowManager mWindowManager;
+
+    private HandlerThread mHandlerThread;
+    private TrustManagerService.Injector mInjector;
+    private TrustManagerService mService;
+    private ITrustManager mTrustManager;
 
     @Before
-    public void setUp() {
-        resetTrustAgentLockSettings();
-        LocalServices.addService(SystemServiceManager.class, mock(SystemServiceManager.class));
+    public void setUp() throws Exception {
+        when(mActivityManager.isUserRunning(TEST_USER_ID)).thenReturn(true);
+
+        when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
+        when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
+        when(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).thenReturn(mKnownTrustAgents);
+        when(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).thenReturn(mEnabledTrustAgents);
+        doAnswer(invocation -> {
+            mKnownTrustAgents.clear();
+            mKnownTrustAgents.addAll((Collection<ComponentName>) invocation.getArgument(0));
+            return null;
+        }).when(mLockPatternUtils).setKnownTrustAgents(any(), eq(TEST_USER_ID));
+        doAnswer(invocation -> {
+            mEnabledTrustAgents.clear();
+            mEnabledTrustAgents.addAll((Collection<ComponentName>) invocation.getArgument(0));
+            return null;
+        }).when(mLockPatternUtils).setEnabledTrustAgents(any(), eq(TEST_USER_ID));
 
         ArgumentMatcher<Intent> trustAgentIntentMatcher = new ArgumentMatcher<Intent>() {
             @Override
@@ -112,17 +135,42 @@
                 return TrustAgentService.SERVICE_INTERFACE.equals(argument.getAction());
             }
         };
-        when(mPackageManagerMock.queryIntentServicesAsUser(argThat(trustAgentIntentMatcher),
+        when(mPackageManager.queryIntentServicesAsUser(argThat(trustAgentIntentMatcher),
                 anyInt(), anyInt())).thenReturn(mTrustAgentResolveInfoList);
-        when(mPackageManagerMock.checkPermission(any(), any())).thenReturn(
+        when(mPackageManager.checkPermission(any(), any())).thenReturn(
                 PackageManager.PERMISSION_GRANTED);
-        mMockContext.setMockPackageManager(mPackageManagerMock);
+
+        when(mUserManager.getAliveUsers()).thenReturn(
+                List.of(new UserInfo(TEST_USER_ID, "user", UserInfo.FLAG_FULL)));
+
+        when(mWindowManager.isKeyguardLocked()).thenReturn(true);
+
+        mMockContext.addMockSystemService(ActivityManager.class, mActivityManager);
+        mMockContext.setMockPackageManager(mPackageManager);
+        mMockContext.addMockSystemService(UserManager.class, mUserManager);
+        doReturn(mWindowManager).when(() -> WindowManagerGlobal.getWindowManagerService());
+        LocalServices.addService(SystemServiceManager.class, mock(SystemServiceManager.class));
+
+        grantPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE);
+        grantPermission(Manifest.permission.TRUST_LISTENER);
+
+        mHandlerThread = new HandlerThread("handler");
+        mHandlerThread.start();
+        mInjector = new TrustManagerService.Injector(mLockPatternUtils, mHandlerThread.getLooper());
+        mService = new TrustManagerService(mMockContext, mInjector);
+
+        // Get the ITrustManager from the new TrustManagerService.
+        mService.onStart();
+        ArgumentCaptor<IBinder> binderArgumentCaptor = ArgumentCaptor.forClass(IBinder.class);
+        verify(() -> ServiceManager.addService(eq(Context.TRUST_SERVICE),
+                    binderArgumentCaptor.capture(), anyBoolean(), anyInt()));
+        mTrustManager = ITrustManager.Stub.asInterface(binderArgumentCaptor.getValue());
     }
 
     @After
     public void tearDown() {
-        resetTrustAgentLockSettings();
         LocalServices.removeServiceForTest(SystemServiceManager.class);
+        mHandlerThread.quit();
     }
 
     @Test
@@ -142,10 +190,9 @@
 
         bootService();
 
-        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
-                systemTrustAgent1, systemTrustAgent2);
-        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
-                systemTrustAgent1, systemTrustAgent2, userTrustAgent1, userTrustAgent2);
+        assertThat(mEnabledTrustAgents).containsExactly(systemTrustAgent1, systemTrustAgent2);
+        assertThat(mKnownTrustAgents).containsExactly(systemTrustAgent1, systemTrustAgent2,
+                    userTrustAgent1, userTrustAgent2);
     }
 
     @Test
@@ -162,10 +209,8 @@
 
         bootService();
 
-        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
-                defaultTrustAgent);
-        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
-                systemTrustAgent, defaultTrustAgent);
+        assertThat(mEnabledTrustAgents).containsExactly(defaultTrustAgent);
+        assertThat(mKnownTrustAgents).containsExactly(systemTrustAgent, defaultTrustAgent);
     }
 
     @Test
@@ -174,16 +219,16 @@
                 "com.android/.SystemTrustAgent");
         ComponentName trustAgent2 = ComponentName.unflattenFromString(
                 "com.android/.AnotherSystemTrustAgent");
-        initializeEnabledAgents(trustAgent1);
+        mEnabledTrustAgents.add(trustAgent1);
+        Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
+                Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
         addTrustAgent(trustAgent1, /* isSystemApp= */ true);
         addTrustAgent(trustAgent2, /* isSystemApp= */ true);
 
         bootService();
 
-        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
-                trustAgent1);
-        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
-                trustAgent1, trustAgent2);
+        assertThat(mEnabledTrustAgents).containsExactly(trustAgent1);
+        assertThat(mKnownTrustAgents).containsExactly(trustAgent1, trustAgent2);
     }
 
     @Test
@@ -192,17 +237,17 @@
                 "com.android/.SystemTrustAgent");
         ComponentName trustAgent2 = ComponentName.unflattenFromString(
                 "com.android/.AnotherSystemTrustAgent");
-        initializeEnabledAgents(trustAgent1);
-        initializeKnownAgents(trustAgent1);
+        Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
+                Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
+        Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
+                Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
         addTrustAgent(trustAgent1, /* isSystemApp= */ true);
         addTrustAgent(trustAgent2, /* isSystemApp= */ true);
 
         bootService();
 
-        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
-                trustAgent1, trustAgent2);
-        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
-                trustAgent1, trustAgent2);
+        assertThat(mEnabledTrustAgents).containsExactly(trustAgent1, trustAgent2);
+        assertThat(mKnownTrustAgents).containsExactly(trustAgent1, trustAgent2);
     }
 
     @Test
@@ -214,10 +259,8 @@
 
         mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
 
-        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
-                newAgentComponentName);
-        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
-                newAgentComponentName);
+        assertThat(mEnabledTrustAgents).containsExactly(newAgentComponentName);
+        assertThat(mKnownTrustAgents).containsExactly(newAgentComponentName);
     }
 
     @Test
@@ -235,10 +278,8 @@
 
         mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
 
-        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
-                defaultTrustAgent);
-        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
-                defaultTrustAgent, newAgentComponentName);
+        assertThat(mEnabledTrustAgents).containsExactly(defaultTrustAgent);
+        assertThat(mKnownTrustAgents).containsExactly(defaultTrustAgent, newAgentComponentName);
     }
 
     @Test
@@ -250,9 +291,8 @@
 
         mMockContext.sendPackageChangedBroadcast(newAgentComponentName);
 
-        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).isEmpty();
-        assertThat(mLockPatternUtils.getKnownTrustAgents(TEST_USER_ID)).containsExactly(
-                newAgentComponentName);
+        assertThat(mEnabledTrustAgents).isEmpty();
+        assertThat(mKnownTrustAgents).containsExactly(newAgentComponentName);
     }
 
     @Test
@@ -265,50 +305,21 @@
         addTrustAgent(systemTrustAgent2, /* isSystemApp= */ true);
         bootService();
         // Simulate user turning off systemTrustAgent2
-        mLockPatternUtils.setEnabledTrustAgents(Collections.singletonList(systemTrustAgent1),
-                TEST_USER_ID);
+        mLockPatternUtils.setEnabledTrustAgents(List.of(systemTrustAgent1), TEST_USER_ID);
 
         mMockContext.sendPackageChangedBroadcast(systemTrustAgent2);
 
-        assertThat(mLockPatternUtils.getEnabledTrustAgents(TEST_USER_ID)).containsExactly(
-                systemTrustAgent1);
+        assertThat(mEnabledTrustAgents).containsExactly(systemTrustAgent1);
     }
 
     @Test
     public void reportEnabledTrustAgentsChangedInformsListener() throws RemoteException {
-        final LockPatternUtils utils = mock(LockPatternUtils.class);
-        final TrustManagerService service = new TrustManagerService(mMockContext,
-                new TrustManagerService.Injector(utils, mLooper.getLooper()));
         final ITrustListener trustListener = mock(ITrustListener.class);
-        final IWindowManager windowManager = mock(IWindowManager.class);
-        final int userId = new Random().nextInt();
-
-        mMockContext.getTestablePermissions().setPermission(Manifest.permission.TRUST_LISTENER,
-                PERMISSION_GRANTED);
-
-        when(utils.getKnownTrustAgents(anyInt())).thenReturn(new ArrayList<>());
-
-        MockitoSession mockSession = mockitoSession()
-                .initMocks(this)
-                .mockStatic(ServiceManager.class)
-                .mockStatic(WindowManagerGlobal.class)
-                .startMocking();
-
-        doReturn(windowManager).when(() -> {
-            WindowManagerGlobal.getWindowManagerService();
-        });
-
-        service.onStart();
-        ArgumentCaptor<IBinder> binderArgumentCaptor = ArgumentCaptor.forClass(IBinder.class);
-        verify(() -> ServiceManager.addService(eq(Context.TRUST_SERVICE),
-                binderArgumentCaptor.capture(), anyBoolean(), anyInt()));
-        ITrustManager manager = ITrustManager.Stub.asInterface(binderArgumentCaptor.getValue());
-        manager.registerTrustListener(trustListener);
-        mLooper.dispatchAll();
-        manager.reportEnabledTrustAgentsChanged(userId);
-        mLooper.dispatchAll();
-        verify(trustListener).onEnabledTrustAgentsChanged(eq(userId));
-        mockSession.finishMocking();
+        mTrustManager.registerTrustListener(trustListener);
+        mService.waitForIdle();
+        mTrustManager.reportEnabledTrustAgentsChanged(TEST_USER_ID);
+        mService.waitForIdle();
+        verify(trustListener).onEnabledTrustAgentsChanged(TEST_USER_ID);
     }
 
     private void addTrustAgent(ComponentName agentComponentName, boolean isSystemApp) {
@@ -327,27 +338,16 @@
         mTrustAgentResolveInfoList.add(resolveInfo);
     }
 
-    private void initializeEnabledAgents(ComponentName... enabledAgents) {
-        mLockPatternUtils.setEnabledTrustAgents(Lists.newArrayList(enabledAgents), TEST_USER_ID);
-        Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
-                Settings.Secure.TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
-    }
-
-    private void initializeKnownAgents(ComponentName... knownAgents) {
-        mLockPatternUtils.setKnownTrustAgents(Lists.newArrayList(knownAgents), TEST_USER_ID);
-        Settings.Secure.putIntForUser(mMockContext.getContentResolver(),
-                Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, 1, TEST_USER_ID);
-    }
-
     private void bootService() {
         mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
         mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
         mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+        mMockContext.sendUserStartedBroadcast();
     }
 
-    private void resetTrustAgentLockSettings() {
-        mLockPatternUtils.setEnabledTrustAgents(Collections.emptyList(), TEST_USER_ID);
-        mLockPatternUtils.setKnownTrustAgents(Collections.emptyList(), TEST_USER_ID);
+    private void grantPermission(String permission) {
+        mMockContext.getTestablePermissions().setPermission(
+                permission, PackageManager.PERMISSION_GRANTED);
     }
 
     /** A mock Context that allows the test process to send protected broadcasts. */
@@ -355,6 +355,8 @@
 
         private final ArrayList<BroadcastReceiver> mPackageChangedBroadcastReceivers =
                 new ArrayList<>();
+        private final ArrayList<BroadcastReceiver> mUserStartedBroadcastReceivers =
+                new ArrayList<>();
 
         MockContext(Context base) {
             super(base);
@@ -369,6 +371,9 @@
             if (filter.hasAction(Intent.ACTION_PACKAGE_CHANGED)) {
                 mPackageChangedBroadcastReceivers.add(receiver);
             }
+            if (filter.hasAction(Intent.ACTION_USER_STARTED)) {
+                mUserStartedBroadcastReceivers.add(receiver);
+            }
             return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
                     scheduler);
         }
@@ -386,5 +391,13 @@
                 receiver.onReceive(this, intent);
             }
         }
+
+        void sendUserStartedBroadcast() {
+            Intent intent = new Intent(Intent.ACTION_USER_STARTED)
+                    .putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID);
+            for (BroadcastReceiver receiver : mUserStartedBroadcastReceivers) {
+                receiver.onReceive(this, intent);
+            }
+        }
     }
 }
diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
index 91d8ceb..a9ff3a1 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -113,7 +114,8 @@
         mTestExecutor.simulateAsyncExecutionOfLastCommand();
 
         // THEN the device vibrates once
-        verify(mVibrator, times(1)).vibrate(any(), any(VibrationAttributes.class));
+        verify(mVibrator, times(1)).vibrate(anyInt(), any(), any(), any(),
+                any(VibrationAttributes.class));
     }
 
     @Test
@@ -129,7 +131,7 @@
         mTestExecutor.simulateAsyncExecutionOfLastCommand();
 
         // THEN the device doesn't vibrate
-        verify(mVibrator, never()).vibrate(any(), any(VibrationAttributes.class));
+        verifyZeroInteractions(mVibrator);
     }
 
     @Test
@@ -145,14 +147,15 @@
         mTestExecutor.simulateAsyncExecutionOfLastCommand();
 
         // THEN the device vibrates once
-        verify(mVibrator, times(1)).vibrate(any(), any(VibrationAttributes.class));
+        verify(mVibrator, times(1)).vibrate(anyInt(), any(), any(), any(),
+                any(VibrationAttributes.class));
     }
 
     @Test
     public void testVibrateDisabled_wirelessCharging() {
         createNotifier();
 
-        // GIVEN the charging vibration is disabeld
+        // GIVEN the charging vibration is disabled
         enableChargingVibration(false);
 
         // WHEN wireless charging starts
@@ -161,7 +164,7 @@
         mTestExecutor.simulateAsyncExecutionOfLastCommand();
 
         // THEN the device doesn't vibrate
-        verify(mVibrator, never()).vibrate(any(), any(VibrationAttributes.class));
+        verifyZeroInteractions(mVibrator);
     }
 
     @Test
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index d752ae4..aec896f 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -97,6 +97,7 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.internal.app.IBatteryStats;
+import com.android.internal.foldables.FoldGracePeriodProvider;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -164,6 +165,7 @@
     @Mock private AttentionManagerInternal mAttentionManagerInternalMock;
     @Mock private DreamManagerInternal mDreamManagerInternalMock;
     @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
+    @Mock private FoldGracePeriodProvider mFoldGracePeriodProvider;
     @Mock private Notifier mNotifierMock;
     @Mock private WirelessChargerDetector mWirelessChargerDetectorMock;
     @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock;
@@ -359,6 +361,11 @@
             DeviceConfigParameterProvider createDeviceConfigParameterProvider() {
                 return mDeviceParameterProvider;
             }
+
+            @Override
+            FoldGracePeriodProvider createFoldGracePeriodProvider() {
+                return mFoldGracePeriodProvider;
+            }
         });
         return mService;
     }
@@ -520,6 +527,8 @@
 
     @Test
     public void testWakefulnessSleep_SoftSleepFlag_NoWakelocks() {
+        when(mFoldGracePeriodProvider.isEnabled()).thenReturn(false);
+
         createService();
         // Start with AWAKE state
         startSystem();
@@ -533,6 +542,23 @@
     }
 
     @Test
+    public void testWakefulnessAwakeShowKeyguard_SoftSleepFlag_NoWakelocks() {
+        when(mFoldGracePeriodProvider.isEnabled()).thenReturn(true);
+
+        createService();
+        // Start with AWAKE state
+        startSystem();
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+
+        // Take a nap and verify we stay awake and the keyguard is requested
+        mService.getBinderServiceInstance().goToSleep(mClock.now(),
+                PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
+                PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
+        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+        verify(mNotifierMock).showDismissibleKeyguard();
+    }
+
+    @Test
     public void testWakefulnessSleep_SoftSleepFlag_WithPartialWakelock() {
         createService();
         // Start with AWAKE state
@@ -548,7 +574,7 @@
                 null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
                 null /* callback */);
 
-        // Take a nap and verify we stay awake.
+        // Take a nap and verify we go to sleep.
         mService.getBinderServiceInstance().goToSleep(mClock.now(),
                 PowerManager.GO_TO_SLEEP_REASON_APPLICATION,
                 PowerManager.GO_TO_SLEEP_FLAG_SOFT_SLEEP);
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index a2f8c8b..6b85a32 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -25,10 +25,8 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.ContentResolver;
@@ -1223,35 +1221,6 @@
                 StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR));
     }
 
-    @Test
-    public void testInputMethodSettings_SwitchCurrentUser() {
-        TestContext ownerUserContext = createMockContext(0 /* userId */);
-        final InputMethodInfo systemIme = createFakeInputMethodInfo(
-                "SystemIme", "fake.latin", true /* isSystem */);
-        final InputMethodInfo nonSystemIme = createFakeInputMethodInfo("NonSystemIme",
-                "fake.voice0", false /* isSystem */);
-        final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
-        methodMap.put(systemIme.getId(), systemIme);
-
-        // Init InputMethodSettings for the owner user (userId=0), verify calls can get the
-        // corresponding user's context, contentResolver and the resources configuration.
-        InputMethodUtils.InputMethodSettings settings = new InputMethodUtils.InputMethodSettings(
-                methodMap, 0 /* userId */);
-        assertEquals(0, settings.getCurrentUserId());
-
-        settings.getEnabledInputMethodSubtypeListLocked(nonSystemIme, true);
-        verify(ownerUserContext.getResources(), atLeastOnce()).getConfiguration();
-
-        // Calling switchCurrentUser to the secondary user (userId=10), verify calls can get the
-        // corresponding user's context, contentResolver and the resources configuration.
-        settings.switchCurrentUser(10 /* userId */);
-        assertEquals(10, settings.getCurrentUserId());
-
-        settings.getEnabledInputMethodSubtypeListLocked(nonSystemIme, true);
-        verify(TestContext.getSecondaryUserContext().getResources(),
-                atLeastOnce()).getConfiguration();
-    }
-
     private static IntArray createSubtypeHashCodeArrayFromStr(String subtypeHashCodesStr) {
         final IntArray subtypes = new IntArray();
         final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index f049b33..f5282cb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -143,7 +143,6 @@
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
-import android.util.MergedConfiguration;
 import android.util.MutableBoolean;
 import android.view.DisplayInfo;
 import android.view.IRemoteAnimationFinishedCallback;
@@ -395,8 +394,7 @@
         activity.setState(RESUMED, "Testing");
 
         task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
-        activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
-                activity.getConfiguration()));
+        activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
 
         activity.info.configChanges &= ~CONFIG_ORIENTATION;
         final Configuration newConfig = new Configuration(task.getConfiguration());
@@ -420,8 +418,7 @@
         activity.setState(RESUMED, "Testing");
 
         task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
-        activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
-                activity.getConfiguration()));
+        activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
 
         activity.info.configChanges &= ~CONFIG_ORIENTATION;
         final Configuration newConfig = new Configuration(task.getConfiguration());
@@ -447,8 +444,7 @@
         activity.setState(RESUMED, "Testing");
 
         task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
-        activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
-                activity.getConfiguration()));
+        activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
 
         activity.info.configChanges &= ~CONFIG_ORIENTATION;
         final Configuration newConfig = new Configuration(task.getConfiguration());
@@ -468,8 +464,7 @@
         activity.setState(RESUMED, "Testing");
 
         task.onRequestedOverrideConfigurationChanged(task.getConfiguration());
-        activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
-                activity.getConfiguration()));
+        activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
 
         activity.info.configChanges &= ~ActivityInfo.CONFIG_FONT_SCALE;
         final Configuration newConfig = new Configuration(task.getConfiguration());
@@ -571,8 +566,7 @@
                 .build();
         activity.setState(RESUMED, "Testing");
 
-        activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
-                activity.getConfiguration()));
+        activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
 
         clearInvocations(mClientLifecycleManager);
 
@@ -799,8 +793,7 @@
             doReturn(false).when(stack).isTranslucent(any());
             assertTrue(task.shouldBeVisible(null /* starting */));
 
-            activity.setLastReportedConfiguration(new MergedConfiguration(new Configuration(),
-                    activity.getConfiguration()));
+            activity.setLastReportedConfiguration(new Configuration(), activity.getConfiguration());
 
             final Configuration newConfig = new Configuration(activity.getConfiguration());
             final int shortSide = newConfig.screenWidthDp == newConfig.screenHeightDp
@@ -846,7 +839,7 @@
     }
 
     @Test
-    public void testTakeOptions() {
+    public void testTakeSceneTransitionInfo() {
         final ActivityRecord activity = createActivityWithTask();
         ActivityOptions opts = ActivityOptions.makeRemoteAnimation(
                 new RemoteAnimationAdapter(new Stub() {
@@ -864,7 +857,9 @@
                     }
                 }, 0, 0));
         activity.updateOptionsLocked(opts);
-        assertNotNull(activity.takeOptions());
+        // Ensure the SceneTransitionInfo is null (since the ActivityOptions is for remote
+        // animation and AR#takeSceneTransitionInfo also clear the AR#mPendingOptions
+        assertNull(activity.takeSceneTransitionInfo());
         assertNull(activity.getOptions());
 
         final AppTransition appTransition = activity.mDisplayContent.mAppTransition;
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index ac18f80..d83824a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -464,9 +464,10 @@
         // Simulate ActivityOptions#makeSceneTransitionAnimation
         final Bundle myBundle = new Bundle();
         myBundle.putInt(ActivityOptions.KEY_ANIM_TYPE, ANIM_SCENE_TRANSITION);
-        myBundle.putParcelable("android:activity.transitionCompleteListener",
-                mock(android.os.ResultReceiver.class));
         final ActivityOptions options = new ActivityOptions(myBundle);
+        final ActivityOptions.SceneTransitionInfo info = new ActivityOptions.SceneTransitionInfo();
+        info.setResultReceiver(mock(android.os.ResultReceiver.class));
+        options.setSceneTransitionInfo(info);
 
         final ActivityRecord testActivity = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
index dfa595c..99d354a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
@@ -55,6 +55,11 @@
 @RunWith(WindowTestRunner.class)
 public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
 
+    // The fields to override the current DisplayInfo.
+    private String mUniqueId;
+    private int mColorMode;
+    private int mLogicalDensityDpi;
+
     @Override
     protected void onBeforeSystemServicesCreated() {
         // Set other flags to their default values
@@ -73,7 +78,7 @@
     public void testUpdate_deferrableFieldChangedTransitionStarted_deferrableFieldUpdated() {
         performInitialDisplayUpdate();
 
-        givenDisplayInfo(/* uniqueId= */ "old");
+        mUniqueId = "old";
         Runnable onUpdated = mock(Runnable.class);
         mDisplayContent.requestDisplayUpdate(onUpdated);
 
@@ -82,11 +87,21 @@
         verify(onUpdated).run();
         clearInvocations(mDisplayContent.mTransitionController, onUpdated);
 
-        givenDisplayInfo(/* uniqueId= */ "new");
+        mUniqueId = "new";
         mDisplayContent.requestDisplayUpdate(onUpdated);
         captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
         verify(onUpdated).run();
+        verify(mDisplayContent.mTransitionController).requestStartTransition(
+                any(), any(), any(), any());
         assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new");
+        clearInvocations(mDisplayContent.mTransitionController, onUpdated);
+
+        mLogicalDensityDpi += 100;
+        mDisplayContent.requestDisplayUpdate(onUpdated);
+        captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
+        verify(onUpdated).run();
+        verify(mDisplayContent.mTransitionController).requestStartTransition(
+                any(), any(), any(), any());
     }
 
     @Test
@@ -94,7 +109,8 @@
         performInitialDisplayUpdate();
 
         // Update only color mode (non-deferrable field) and keep the same unique id
-        givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 123);
+        mUniqueId = "initial_unique_id";
+        mColorMode = 123;
         Runnable onUpdated = mock(Runnable.class);
         mDisplayContent.requestDisplayUpdate(onUpdated);
 
@@ -107,7 +123,8 @@
         performInitialDisplayUpdate();
 
         // Update only color mode (non-deferrable field) and keep the same unique id
-        givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 123);
+        mUniqueId = "initial_unique_id";
+        mColorMode = 123;
         mDisplayContent.requestDisplayUpdate(mock(Runnable.class));
 
         assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123);
@@ -116,7 +133,7 @@
 
         // Update unique id (deferrable field), keep the same color mode,
         // this update should be deferred
-        givenDisplayInfo(/* uniqueId= */ "new_unique_id", /* colorMode= */ 123);
+        mUniqueId = "new_unique_id";
         mDisplayContent.requestDisplayUpdate(mock(Runnable.class));
 
         assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123);
@@ -126,7 +143,7 @@
         // Update color mode again and keep the same unique id, color mode update
         // should not be deferred, unique id update is still deferred as transition
         // has not started collecting yet
-        givenDisplayInfo(/* uniqueId= */ "new_unique_id", /* colorMode= */ 456);
+        mColorMode = 456;
         Runnable onUpdated = mock(Runnable.class);
         mDisplayContent.requestDisplayUpdate(onUpdated);
 
@@ -146,14 +163,14 @@
     @Test
     public void testUpdate_deferrableFieldUpdatedTransitionPending_fieldNotUpdated() {
         performInitialDisplayUpdate();
-        givenDisplayInfo(/* uniqueId= */ "old");
+        mUniqueId = "old";
         Runnable onUpdated = mock(Runnable.class);
         mDisplayContent.requestDisplayUpdate(onUpdated);
         captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true);
         verify(onUpdated).run();
         clearInvocations(mDisplayContent.mTransitionController, onUpdated);
 
-        givenDisplayInfo(/* uniqueId= */ "new");
+        mUniqueId = "new";
         mDisplayContent.requestDisplayUpdate(onUpdated);
 
         captureStartTransitionCollection(); // do not continue by not starting the collection
@@ -164,7 +181,7 @@
     @Test
     public void testTwoDisplayUpdates_transitionStarted_displayUpdated() {
         performInitialDisplayUpdate();
-        givenDisplayInfo(/* uniqueId= */ "old");
+        mUniqueId = "old";
         Runnable onUpdated = mock(Runnable.class);
         mDisplayContent.requestDisplayUpdate(onUpdated);
         captureStartTransitionCollection().getValue()
@@ -173,10 +190,10 @@
         clearInvocations(mDisplayContent.mTransitionController, onUpdated);
 
         // Perform two display updates while WM is 'busy'
-        givenDisplayInfo(/* uniqueId= */ "new1");
+        mUniqueId = "new1";
         Runnable onUpdated1 = mock(Runnable.class);
         mDisplayContent.requestDisplayUpdate(onUpdated1);
-        givenDisplayInfo(/* uniqueId= */ "new2");
+        mUniqueId = "new2";
         Runnable onUpdated2 = mock(Runnable.class);
         mDisplayContent.requestDisplayUpdate(onUpdated2);
 
@@ -215,22 +232,19 @@
         return callbackCaptor;
     }
 
-    private void givenDisplayInfo(String uniqueId) {
-        givenDisplayInfo(uniqueId, /* colorMode= */ 0);
-    }
+    private void performInitialDisplayUpdate() {
+        mUniqueId = "initial_unique_id";
+        mColorMode = 0;
+        mLogicalDensityDpi = 400;
 
-    private void givenDisplayInfo(String uniqueId, int colorMode) {
         spyOn(mDisplayContent.mDisplay);
         doAnswer(invocation -> {
             DisplayInfo info = invocation.getArgument(0);
-            info.uniqueId = uniqueId;
-            info.colorMode = colorMode;
+            info.uniqueId = mUniqueId;
+            info.colorMode = mColorMode;
+            info.logicalDensityDpi = mLogicalDensityDpi;
             return null;
         }).when(mDisplayContent.mDisplay).getDisplayInfo(any());
-    }
-
-    private void performInitialDisplayUpdate() {
-        givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 0);
         Runnable onUpdated = mock(Runnable.class);
         mDisplayContent.requestDisplayUpdate(onUpdated);
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 28fecd6..6013063 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -76,7 +76,6 @@
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
-import android.util.MergedConfiguration;
 import android.util.Pair;
 
 import androidx.test.filters.MediumTest;
@@ -545,8 +544,7 @@
         assertNotEquals(activity.getConfiguration().orientation, rotatedConfig.orientation);
         // Assume the activity was shown in different orientation. For example, the top activity is
         // landscape and the portrait lockscreen is shown.
-        activity.setLastReportedConfiguration(
-                new MergedConfiguration(mAtm.getGlobalConfiguration(), rotatedConfig));
+        activity.setLastReportedConfiguration(mAtm.getGlobalConfiguration(), rotatedConfig);
         activity.setState(STOPPED, "sleep");
 
         display.setIsSleeping(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index 810cbe8..be30593 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -134,6 +134,14 @@
         assertFalse(r.isSyncFinished(r.getSyncGroup()));
         r.finishRelaunching();
         assertTrue(r.isSyncFinished(r.getSyncGroup()));
+        assertEquals(SYNC_STATE_READY, r.mSyncState);
+
+        // If the container has finished the sync, isSyncFinished should not change the sync state.
+        final BLASTSyncEngine.SyncGroup syncGroup = r.getSyncGroup();
+        r.finishSync(mTransaction, syncGroup, false /* cancel */);
+        assertEquals(SYNC_STATE_NONE, r.mSyncState);
+        assertTrue(r.isSyncFinished(syncGroup));
+        assertEquals(SYNC_STATE_NONE, r.mSyncState);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 52e2d8a..7551b165 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -330,6 +330,10 @@
     }
 
     @Override
+    public void showDismissibleKeyguard() {
+    }
+
+    @Override
     public void setPipVisibilityLw(boolean visible) {
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 8bf4833..21fee72 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -37,6 +37,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
@@ -85,6 +86,7 @@
 import android.view.InputChannel;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
+import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.WindowInsets;
@@ -952,6 +954,57 @@
     }
 
     @Test
+    public void testDrawMagnifiedViewport() {
+        final int displayId = mDisplayContent.mDisplayId;
+        // Use real surface, so ViewportWindow's BlastBufferQueue can be created.
+        final ArrayList<SurfaceControl> surfaceControls = new ArrayList<>();
+        mWm.mSurfaceControlFactory = s -> new SurfaceControl.Builder() {
+            @Override
+            public SurfaceControl build() {
+                final SurfaceControl sc = super.build();
+                surfaceControls.add(sc);
+                return sc;
+            }
+        };
+        mWm.mAccessibilityController.setMagnificationCallbacks(displayId,
+                mock(WindowManagerInternal.MagnificationCallbacks.class));
+        final boolean[] lockCanvasInWmLock = { false };
+        final Surface surface = mWm.mAccessibilityController.forceShowMagnifierSurface(displayId);
+        spyOn(surface);
+        doAnswer(invocationOnMock -> {
+            lockCanvasInWmLock[0] |= Thread.holdsLock(mWm.mGlobalLock);
+            invocationOnMock.callRealMethod();
+            return null;
+        }).when(surface).lockCanvas(any());
+        mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId, mTransaction);
+        waitUntilHandlersIdle();
+        try {
+            verify(surface).lockCanvas(any());
+
+            clearInvocations(surface);
+            // Invalidate and redraw.
+            mWm.mAccessibilityController.onDisplaySizeChanged(mDisplayContent);
+            mWm.mAccessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId, mTransaction);
+            // Turn off magnification to release surface.
+            mWm.mAccessibilityController.setMagnificationCallbacks(displayId, null);
+            if (!com.android.window.flags.Flags.drawMagnifierBorderOutsideWmlock()) {
+                verify(surface).release();
+                assertTrue(lockCanvasInWmLock[0]);
+                return;
+            }
+            waitUntilHandlersIdle();
+            // lockCanvas must not be called after releasing.
+            verify(surface, never()).lockCanvas(any());
+            verify(surface).release();
+            assertFalse(lockCanvasInWmLock[0]);
+        } finally {
+            for (int i = surfaceControls.size() - 1; i >= 0; --i) {
+                surfaceControls.get(i).release();
+            }
+        }
+    }
+
+    @Test
     public void testRequestKeyboardShortcuts_noWindow() {
         doNothing().when(mWm.mContext).enforceCallingOrSelfPermission(anyString(), anyString());
         doReturn(null).when(mWm).getFocusedWindowLocked();
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index d05eb5c..57b13e9 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -422,8 +422,8 @@
             "android.telecom.extra.CALL_CREATED_TIME_MILLIS";
 
     /**
-     * The extra for call log uri that was used to mark missed calls as read when dialer gets the
-     * notification on reboot.
+     * Extra URI that is used by a dialer to query the {@link android.provider.CallLog} content
+     * provider and associate a missed call notification with a call log entry.
      */
     @FlaggedApi(Flags.FLAG_ADD_CALL_URI_FOR_MISSED_CALLS)
     public static final String EXTRA_CALL_LOG_URI =
diff --git a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
index 61d7ead..c902016 100644
--- a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
+++ b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
@@ -169,6 +169,7 @@
     public static final int SECURITY_ALGORITHM_NEA1 = 56;
     public static final int SECURITY_ALGORITHM_NEA2 = 57;
     public static final int SECURITY_ALGORITHM_NEA3 = 58;
+    public static final int SECURITY_ALGORITHM_IMS_NULL = 67;
     public static final int SECURITY_ALGORITHM_SIP_NULL = 68;
     public static final int SECURITY_ALGORITHM_AES_GCM = 69;
     public static final int SECURITY_ALGORITHM_AES_GMAC = 70;
@@ -176,9 +177,8 @@
     public static final int SECURITY_ALGORITHM_DES_EDE3_CBC = 72;
     public static final int SECURITY_ALGORITHM_AES_EDE3_CBC = 73;
     public static final int SECURITY_ALGORITHM_HMAC_SHA1_96 = 74;
-    public static final int SECURITY_ALGORITHM_HMAC_SHA1_96_NULL = 75;
-    public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 76;
-    public static final int SECURITY_ALGORITHM_HMAC_MD5_96_NULL = 77;
+    public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 75;
+    public static final int SECURITY_ALGORITHM_SRTP_NULL = 86;
     public static final int SECURITY_ALGORITHM_SRTP_AES_COUNTER = 87;
     public static final int SECURITY_ALGORITHM_SRTP_AES_F8 = 88;
     public static final int SECURITY_ALGORITHM_SRTP_HMAC_SHA1 = 89;
@@ -199,15 +199,15 @@
             SECURITY_ALGORITHM_UEA2, SECURITY_ALGORITHM_EEA0, SECURITY_ALGORITHM_EEA1,
             SECURITY_ALGORITHM_EEA2, SECURITY_ALGORITHM_EEA3, SECURITY_ALGORITHM_NEA0,
             SECURITY_ALGORITHM_NEA1, SECURITY_ALGORITHM_NEA2, SECURITY_ALGORITHM_NEA3,
-            SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM,
+            SECURITY_ALGORITHM_IMS_NULL, SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM,
             SECURITY_ALGORITHM_AES_GMAC, SECURITY_ALGORITHM_AES_CBC,
             SECURITY_ALGORITHM_DES_EDE3_CBC, SECURITY_ALGORITHM_AES_EDE3_CBC,
-            SECURITY_ALGORITHM_HMAC_SHA1_96, SECURITY_ALGORITHM_HMAC_SHA1_96_NULL,
-            SECURITY_ALGORITHM_HMAC_MD5_96, SECURITY_ALGORITHM_HMAC_MD5_96_NULL,
-            SECURITY_ALGORITHM_SRTP_AES_COUNTER, SECURITY_ALGORITHM_SRTP_AES_F8,
-            SECURITY_ALGORITHM_SRTP_HMAC_SHA1, SECURITY_ALGORITHM_ENCR_AES_GCM_16,
-            SECURITY_ALGORITHM_ENCR_AES_CBC, SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128,
-            SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX})
+            SECURITY_ALGORITHM_HMAC_SHA1_96, SECURITY_ALGORITHM_HMAC_MD5_96,
+            SECURITY_ALGORITHM_SRTP_NULL, SECURITY_ALGORITHM_SRTP_AES_COUNTER,
+            SECURITY_ALGORITHM_SRTP_AES_F8, SECURITY_ALGORITHM_SRTP_HMAC_SHA1,
+            SECURITY_ALGORITHM_ENCR_AES_GCM_16, SECURITY_ALGORITHM_ENCR_AES_CBC,
+            SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128, SECURITY_ALGORITHM_UNKNOWN,
+            SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX})
     public @interface SecurityAlgorithm {
     }
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 9e292be..1b47dfe 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -510,7 +510,7 @@
     /** @hide */
     @UnsupportedAppUsage
     public TelephonyManager(Context context) {
-      this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
+        this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
     }
 
     /** @hide */
@@ -2140,10 +2140,14 @@
      * the IMEI/SV for GSM phones. Return null if the software version is
      * not available.
      * <p>
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY}.
      */
     @RequiresPermission(anyOf = {
             android.Manifest.permission.READ_PHONE_STATE,
             android.Manifest.permission.READ_BASIC_PHONE_STATE})
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
     @Nullable
     public String getDeviceSoftwareVersion() {
         return getDeviceSoftwareVersion(getSlotIndex());
@@ -2158,10 +2162,13 @@
      *
      * @param slotIndex of which deviceID is returned
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
     @Nullable
     public String getDeviceSoftwareVersion(int slotIndex) {
         ITelephony telephony = getITelephony();
@@ -2288,6 +2295,9 @@
      *
      * See {@link #getImei(int)} for details on the required permissions and behavior
      * when the caller does not hold sufficient permissions.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_GSM}.
      */
     @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -2330,6 +2340,9 @@
      * </ul>
      *
      * @param slotIndex of which IMEI is returned
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_GSM}.
      */
     @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -2350,6 +2363,9 @@
     /**
      * Returns the Type Allocation Code from the IMEI. Return null if Type Allocation Code is not
      * available.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_GSM}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
     @Nullable
@@ -2362,6 +2378,9 @@
      * available.
      *
      * @param slotIndex of which Type Allocation Code is returned
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_GSM}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_GSM)
     @Nullable
@@ -2407,6 +2426,9 @@
      *     the READ_PHONE_STATE permission, or if the calling app is targeting API level 29 or
      *     higher, then a SecurityException is thrown.</li>
      * </ul>
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
      */
     @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -2446,6 +2468,9 @@
      * </ul>
      *
      * @param slotIndex of which MEID is returned
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
      */
     @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -2472,6 +2497,9 @@
     /**
      * Returns the Manufacturer Code from the MEID. Return null if Manufacturer Code is not
      * available.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
     @Nullable
@@ -2484,6 +2512,9 @@
      * available.
      *
      * @param slotIndex of which Type Allocation Code is returned
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
     @Nullable
@@ -2528,6 +2559,9 @@
      *     the READ_PHONE_STATE permission, or if the calling app is targeting API level 29 or
      *     higher, then a SecurityException is thrown.</li>
      * </ul>
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -2563,10 +2597,14 @@
      *<p>
      * @return Current location of the device or null if not available.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
+     *
      * @deprecated use {@link #getAllCellInfo} instead, which returns a superset of this API.
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public CellLocation getCellLocation() {
         try {
             ITelephony telephony = getITelephony();
@@ -2596,12 +2634,15 @@
      *
      * @return List of NeighboringCellInfo or null if info unavailable.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @removed
      * @deprecated Use {@link #getAllCellInfo} which returns a superset of the information
      *             from NeighboringCellInfo, including LTE cell information.
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public List<NeighboringCellInfo> getNeighboringCellInfo() {
         try {
             ITelephony telephony = getITelephony();
@@ -2648,9 +2689,12 @@
      * @see #PHONE_TYPE_CDMA
      * @see #PHONE_TYPE_SIP
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY}.
      * {@hide}
      */
     @SystemApi
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
     public int getCurrentPhoneType() {
         return getCurrentPhoneType(getSubId());
     }
@@ -2663,9 +2707,13 @@
      * @see #PHONE_TYPE_CDMA
      *
      * @param subId for which phone type is returned
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY}.
      * @hide
      */
     @SystemApi
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
     public int getCurrentPhoneType(int subId) {
         int phoneId;
         if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
@@ -2712,7 +2760,11 @@
      * @see #PHONE_TYPE_GSM
      * @see #PHONE_TYPE_CDMA
      * @see #PHONE_TYPE_SIP
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
     public int getPhoneType() {
         if (!isVoiceCapable()) {
             return PHONE_TYPE_NONE;
@@ -2912,6 +2964,9 @@
      * @see CarrierConfigManager#getConfigForSubId(int)
      * @see #createForSubscriptionId(int)
      * @see #createForPhoneAccountHandle(PhoneAccountHandle)
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @WorkerThread
@@ -2958,6 +3013,9 @@
      * <p>
      * @return the lowercase 2 character ISO-3166-1 alpha-2 country code, or empty string if not
      * available.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public String getNetworkCountryIso() {
@@ -2980,6 +3038,8 @@
      * available.
      *
      * @throws IllegalArgumentException when the slotIndex is invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      *
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -3108,9 +3168,13 @@
      *
      * @deprecated use {@link #getDataNetworkType()}
      * @return the NETWORK_TYPE_xxxx for current data connection.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public @NetworkType int getNetworkType() {
         return getNetworkType(getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
     }
@@ -3199,12 +3263,15 @@
      * @see #NETWORK_TYPE_EHRPD
      * @see #NETWORK_TYPE_HSPAP
      * @see #NETWORK_TYPE_NR
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(anyOf = {
             android.Manifest.permission.READ_PHONE_STATE,
             android.Manifest.permission.READ_BASIC_PHONE_STATE})
-    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public @NetworkType int getDataNetworkType() {
         return getDataNetworkType(getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
     }
@@ -3245,6 +3312,9 @@
      * or {@link android.Manifest.permission#READ_BASIC_PHONE_STATE
      * READ_BASIC_PHONE_STATE} or that the calling app has carrier privileges
      * (see {@link #hasCarrierPrivileges}).
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(anyOf = {
@@ -3597,6 +3667,9 @@
      * of whether an active SIM profile is present or not so this API would always return true.
      *
      * @return true if a ICC card is present.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public boolean hasIccCard() {
@@ -3640,6 +3713,9 @@
      * @see #SIM_STATE_PERM_DISABLED
      * @see #SIM_STATE_CARD_IO_ERROR
      * @see #SIM_STATE_CARD_RESTRICTED
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public @SimState int getSimState() {
@@ -3681,6 +3757,8 @@
      * @see #SIM_STATE_CARD_RESTRICTED
      * @see #SIM_STATE_PRESENT
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -3701,11 +3779,14 @@
      * @see #SIM_STATE_CARD_RESTRICTED
      * @see #SIM_STATE_PRESENT
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      * @deprecated instead use {@link #getSimCardState(int, int)}
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     @Deprecated
     public @SimState int getSimCardState(int physicalSlotIndex) {
         int activePort = getFirstActivePortIndex(physicalSlotIndex);
@@ -3727,6 +3808,8 @@
      * @see #SIM_STATE_CARD_RESTRICTED
      * @see #SIM_STATE_PRESENT
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -3785,6 +3868,8 @@
      * @see #SIM_STATE_PERM_DISABLED
      * @see #SIM_STATE_LOADED
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -3808,11 +3893,14 @@
      * @see #SIM_STATE_PERM_DISABLED
      * @see #SIM_STATE_LOADED
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      * @deprecated instead use {@link #getSimApplicationState(int, int)}
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     @Deprecated
     public @SimState int getSimApplicationState(int physicalSlotIndex) {
         int activePort = getFirstActivePortIndex(physicalSlotIndex);
@@ -3836,6 +3924,8 @@
      * @see #SIM_STATE_PERM_DISABLED
      * @see #SIM_STATE_LOADED
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -3876,6 +3966,9 @@
      *
      * @param appType the uicc app type like {@link APPTYPE_CSIM}
      * @return true if the specified type of application in UICC CARD or false if no uicc or error.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -3908,6 +4001,9 @@
      * @see #SIM_STATE_PERM_DISABLED
      * @see #SIM_STATE_CARD_IO_ERROR
      * @see #SIM_STATE_CARD_RESTRICTED
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public @SimState int getSimState(int slotIndex) {
@@ -4105,6 +4201,9 @@
      *     the READ_PHONE_STATE permission, or if the calling app is targeting API level 29 or
      *     higher, then a SecurityException is thrown.</li>
      * </ul>
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -4172,6 +4271,9 @@
      *
      * @return {@code true} if 3GPP and 3GPP2 radio technologies can be supported at the same time
      *         {@code false} if not supported or unknown
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @SystemApi
@@ -4219,6 +4321,9 @@
      * through a factory reset.
      *
      * @return card ID of the default eUICC card, if loaded.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_EUICC)
     public int getCardIdForDefaultEuicc() {
@@ -4252,6 +4357,9 @@
      * @return a list of UiccCardInfo objects, representing information on the currently inserted
      * UICCs and eUICCs. Each UiccCardInfo in the list will have private information filtered out if
      * the caller does not have adequate permissions for that card.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -4276,6 +4384,8 @@
      *
      * @return UiccSlotInfo array.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -4319,6 +4429,9 @@
      * @param physicalSlots The content of the array represents the physical slot index. The array
      *        size should be same as {@link #getUiccSlotsInfo()}.
      * @return boolean Return true if the switch succeeds, false if the switch fails.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      * @deprecated {@link #setSimSlotMapping(Collection, Executor, Consumer)}
      */
@@ -4328,6 +4441,7 @@
     @SystemApi
     @Deprecated
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public boolean switchSlots(int[] physicalSlots) {
         try {
             ITelephony telephony = getITelephony();
@@ -4420,6 +4534,8 @@
      * @throws IllegalArgumentException if the caller passes in an invalid collection of
      *         UiccSlotMapping like duplicate data, etc
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -4452,11 +4568,14 @@
      * @return a map indicates the mapping from logical slots to physical slots. The size of the map
      * should be {@link #getPhoneCount()} if success, otherwise return an empty map.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      * @deprecated use {@link #getSimSlotMapping()} instead.
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     @NonNull
     @Deprecated
     public Map<Integer, Integer> getLogicalToPhysicalSlotMapping() {
@@ -4484,6 +4603,9 @@
      *
      * @return a collection of {@link UiccSlotMapping} which indicates the mapping from logical
      *         slots to ports and physical slots.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -4541,6 +4663,9 @@
      *     the READ_PHONE_STATE permission, or if the calling app is targeting API level 29 or
      *     higher, then a SecurityException is thrown.</li>
      * </ul>
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -4593,6 +4718,8 @@
      *         found, and the carrier does not require a key.
      * @throws IllegalArgumentException when an invalid key is found or when key is required but
      *         not found.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -4638,6 +4765,9 @@
      * Requires Permission: MODIFY_PHONE_STATE.
      *
      * @see #getCarrierInfoForImsiEncryption
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -4840,6 +4970,9 @@
      *                 from disk, as well as on which {@code callback} will be called.
      * @param callback A callback called when the upload operation terminates, either in success
      *                 or in error.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public void uploadCallComposerPicture(@NonNull Path pictureToUpload,
@@ -4947,6 +5080,9 @@
      *                 read, as well as on which the callback will be called.
      * @param callback A callback called when the upload operation terminates, either in success
      *                 or in error.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public void uploadCallComposerPicture(@NonNull InputStream pictureToUpload,
@@ -5081,6 +5217,9 @@
      *
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -5139,6 +5278,8 @@
      *     {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      *     for apps targeting SDK API level 29 and below.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @deprecated use {@link SubscriptionManager#getPhoneNumber(int)} instead.
      */
     @Deprecated
@@ -5148,6 +5289,7 @@
             android.Manifest.permission.READ_SMS,
             android.Manifest.permission.READ_PHONE_NUMBERS
     })
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public String getLine1Number() {
         return getLine1Number(getSubId());
     }
@@ -5214,9 +5356,13 @@
      * @param alphaTag alpha-tagging of the dailing nubmer
      * @param number The dialing number
      * @return true if the operation was executed correctly.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @deprecated use {@link SubscriptionManager#setCarrierPhoneNumber(int, String)} instead.
      */
     @Deprecated
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public boolean setLine1NumberForDisplay(String alphaTag, String number) {
         return setLine1NumberForDisplay(getSubId(), alphaTag, number);
     }
@@ -5336,6 +5482,8 @@
      * {@link SubscriptionManager#createSubscriptionGroup(List)} for the definition of a group,
      * otherwise return an empty array if there is a failure.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -5421,6 +5569,9 @@
      *
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -5459,6 +5610,9 @@
      *
      * @param alphaTag The alpha tag to display.
      * @param number The voicemail number.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public boolean setVoiceMailNumber(String alphaTag, String number) {
@@ -5533,6 +5687,8 @@
      * @see #KEY_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL
      * @see #KEY_VOICEMAIL_SCRAMBLED_PIN_STRING
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      * @hide
      */
     @SystemApi
@@ -5563,6 +5719,9 @@
      * @see #createForSubscriptionId(int)
      * @see #createForPhoneAccountHandle(PhoneAccountHandle)
      * @see VisualVoicemailService
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @Nullable
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@@ -5593,6 +5752,9 @@
      *
      * @see TelecomManager#getDefaultDialerPackage()
      * @see CarrierConfigManager#KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public void setVisualVoicemailSmsFilterSettings(VisualVoicemailSmsFilterSettings settings) {
@@ -5623,6 +5785,9 @@
      *
      * @see SmsManager#sendDataMessage(String, String, short, byte[], PendingIntent, PendingIntent)
      * @see SmsManager#sendTextMessage(String, String, String, PendingIntent, PendingIntent)
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public void sendVisualVoicemailSms(String number, int port, String text,
@@ -5808,6 +5973,9 @@
       * @see #SIM_ACTIVATION_STATE_ACTIVATING
       * @see #SIM_ACTIVATION_STATE_ACTIVATED
       * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+      *
+      * @throws UnsupportedOperationException If the device does not have
+      *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}
       * @hide
       */
     @SystemApi
@@ -5856,6 +6024,9 @@
      * @see #SIM_ACTIVATION_STATE_ACTIVATED
      * @see #SIM_ACTIVATION_STATE_DEACTIVATED
      * @see #SIM_ACTIVATION_STATE_RESTRICTED
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      * @hide
      */
     @SystemApi
@@ -5904,6 +6075,9 @@
      * @see #SIM_ACTIVATION_STATE_ACTIVATING
      * @see #SIM_ACTIVATION_STATE_ACTIVATED
      * @see #SIM_ACTIVATION_STATE_DEACTIVATED
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      * @hide
      */
     @SystemApi
@@ -5954,6 +6128,9 @@
      * @see #SIM_ACTIVATION_STATE_ACTIVATED
      * @see #SIM_ACTIVATION_STATE_DEACTIVATED
      * @see #SIM_ACTIVATION_STATE_RESTRICTED
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      * @hide
      */
     @SystemApi
@@ -6032,6 +6209,9 @@
      *
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -6072,7 +6252,10 @@
      *
      * @throws SecurityException if the caller does not have carrier privileges or is not the
      *         current default dialer
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public void sendDialerSpecialCode(String inputCode) {
         try {
             final ITelephony telephony = getITelephony();
@@ -6143,6 +6326,9 @@
      *
      * <p>Requires Permission:
      * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @Nullable
@@ -6168,6 +6354,9 @@
      * Returns the IMS public user identities (IMPU) that were loaded from the ISIM.
      * @return an array of IMPU strings, with one IMPU per string, or null if
      *      not present or not loaded
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      * @deprecated use {@link #getImsPublicUserIdentities()}
      */
@@ -6199,6 +6388,8 @@
      *         EF_IMPU is not available.
      * @throws IllegalStateException in case the ISIM hasn’t been loaded
      * @throws SecurityException if the caller does not have the required permission/privilege
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @NonNull
@@ -6254,6 +6445,10 @@
      * targeting API level 31+.
      *
      * @return the current call state.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELECOM}.
+     *
      * @deprecated Use {@link #getCallStateForSubscription} to retrieve the call state for a
      * specific telephony subscription (which allows carrier privileged apps),
      * {@link TelephonyCallback.CallStateListener} for real-time call state updates, or
@@ -6261,6 +6456,7 @@
      * device.
      */
     @RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true)
+    @RequiresFeature(PackageManager.FEATURE_TELECOM)
     @Deprecated
     public @CallState int getCallState() {
         if (mContext != null) {
@@ -6281,6 +6477,9 @@
      * @see TelephonyManager#createForSubscriptionId(int)
      * @see TelephonyManager#createForPhoneAccountHandle(PhoneAccountHandle)
      * @return The call state of the subscription associated with this TelephonyManager instance.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
@@ -6341,6 +6540,9 @@
      * @see #DATA_ACTIVITY_OUT
      * @see #DATA_ACTIVITY_INOUT
      * @see #DATA_ACTIVITY_DORMANT
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
     public int getDataActivity() {
@@ -6414,6 +6616,9 @@
      * @see #DATA_SUSPENDED
      * @see #DATA_DISCONNECTING
      * @see #DATA_HANDOVER_IN_PROGRESS
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
     public int getDataState() {
@@ -6599,10 +6804,14 @@
      * Returns the CDMA ERI icon display number. The number is assigned by
      * 3GPP2 C.R1001-H v1.0 Table 8.1-1. Additionally carriers define their own ERI display numbers.
      * Defined values are {@link #ERI_ON}, {@link #ERI_OFF}, and {@link #ERI_FLASH}.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
     public @EriIconIndex int getCdmaEnhancedRoamingIndicatorDisplayNumber() {
         return getCdmaEriIconIndex(getSubId());
     }
@@ -6810,6 +7019,9 @@
      *
      * @return List of {@link android.telephony.CellInfo}; null if cell
      * information is unavailable.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -6905,6 +7117,9 @@
      *
      * @param executor the executor on which callback will be invoked.
      * @param callback a callback to receive CellInfo.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -6966,6 +7181,9 @@
      * @param workSource the requestor to whom the power consumption for this should be attributed.
      * @param executor the executor on which callback will be invoked.
      * @param callback a callback to receive CellInfo.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @SystemApi
@@ -7052,6 +7270,9 @@
 
     /**
      * Returns the MMS user agent.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public String getMmsUserAgent() {
@@ -7068,6 +7289,9 @@
 
     /**
      * Returns the MMS user agent profile URL.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public String getMmsUAProfUrl() {
@@ -7111,8 +7335,12 @@
      *
      * @param AID Application id. See ETSI 102.221 and 101.220.
      * @return an IccOpenLogicalChannelResponse object.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @deprecated Replaced by {@link #iccOpenLogicalChannel(String, int)}
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     @Deprecated
     public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID) {
         return iccOpenLogicalChannel(getSubId(), AID, -1);
@@ -7145,6 +7373,8 @@
      * @param aid Application id. See ETSI 102.221 and 101.220.
      * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      * @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP),
      * instead use {@link #iccOpenLogicalChannelByPort(int, int, String, int)}
@@ -7200,9 +7430,13 @@
      * @param aid Application id. See ETSI 102.221 and 101.220.
      * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     @SystemApi
     @NonNull
     public IccOpenLogicalChannelResponse iccOpenLogicalChannelByPort(int slotIndex,
@@ -7255,6 +7489,9 @@
      * @param AID Application id. See ETSI 102.221 and 101.220.
      * @param p2 P2 parameter (described in ISO 7816-4).
      * @return an IccOpenLogicalChannelResponse object.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID, int p2) {
@@ -7321,6 +7558,9 @@
      * @param channel is the channel id to be closed as returned by a successful
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      * @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP),
      * instead use {@link #iccCloseLogicalChannelByPort(int, int, int)}
@@ -7365,9 +7605,12 @@
      * @throws IllegalStateException if the Telephony process is not currently available or modem
      *                               currently can't process this command.
      * @throws IllegalArgumentException if invalid arguments are passed.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     @SystemApi
     public void iccCloseLogicalChannelByPort(int slotIndex, int portIndex, int channel) {
         try {
@@ -7403,6 +7646,8 @@
      *            iccOpenLogicalChannel.
      * @return true if the channel was closed successfully.
      * @throws IllegalArgumentException if input parameters are wrong. e.g., invalid channel
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public boolean iccCloseLogicalChannel(int channel) {
@@ -7469,6 +7714,8 @@
      * @param data Data to be sent with the APDU.
      * @return The APDU response from the ICC card with the status appended at the end, or null if
      * there is an issue connecting to the Telephony service.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      * @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP),
      * instead use
@@ -7516,9 +7763,13 @@
      * @param data Data to be sent with the APDU.
      * @return The APDU response from the ICC card with the status appended at the end, or null if
      * there is an issue connecting to the Telephony service.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     @SystemApi
     @NonNull
     public String iccTransmitApduLogicalChannelByPort(int slotIndex, int portIndex, int channel,
@@ -7563,6 +7814,9 @@
      * @param data Data to be sent with the APDU.
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public String iccTransmitApduLogicalChannel(int channel, int cla,
@@ -7628,6 +7882,9 @@
      * @param data Data to be sent with the APDU.
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      * @deprecated This API is not compatible on eUICC supporting Multiple Enabled Profile(MEP),
      * instead use
@@ -7673,9 +7930,13 @@
      * @param data Data to be sent with the APDU.
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     @SystemApi
     @NonNull
     public String iccTransmitApduBasicChannelByPort(int slotIndex, int portIndex, int cla,
@@ -7712,6 +7973,9 @@
      * @param data Data to be sent with the APDU.
      * @return The APDU response from the ICC card with the status appended at
      *            the end.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public String iccTransmitApduBasicChannel(int cla,
@@ -7768,6 +8032,9 @@
      * @param p3 P3 value of the APDU command.
      * @param filePath
      * @return The APDU response.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public byte[] iccExchangeSimIO(int fileID, int command, int p1, int p2, int p3,
@@ -7817,6 +8084,9 @@
      * @return The APDU response from the ICC card in hexadecimal format
      *         with the last 4 bytes being the status word. If the command fails,
      *         returns an empty string.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public String sendEnvelopeWithStatus(String content) {
@@ -7978,6 +8248,8 @@
      *
      * @return {@code true} on success; {@code false} on any failure.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -8008,6 +8280,8 @@
      *
      * @deprecated  Using {@link #rebootModem()} instead.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -8035,6 +8309,8 @@
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
      * @throws IllegalStateException if the Telephony process is not currently available.
      * @throws RuntimeException
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -8166,6 +8442,9 @@
      *                      {@link #getMaxNumberVerificationTimeoutMillis()}, whichever is lesser.
      * @param executor The {@link Executor} that callbacks should be executed on.
      * @param callback The callback to use for delivering results.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -8378,6 +8657,9 @@
      * See 3GPP TS 31.103 (Section 4.2.7) for the definition and more information on this table.
      *
      * @return IMS Service Table or null if not present or not loaded
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @Nullable
@@ -8496,6 +8778,9 @@
      *   Key freshness failure
      *   Authentication error, no memory space available
      *   Authentication error, no memory space available in EFMUK
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     // TODO(b/73660190): This should probably require MODIFY_PHONE_STATE, not
     // READ_PRIVILEGED_PHONE_STATE. It certainly shouldn't reference the permission in Javadoc since
@@ -8552,6 +8837,9 @@
      * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
      * @return an array of forbidden PLMNs or null if not available
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -8602,6 +8890,9 @@
      * @return number of PLMNs that were successfully written to the SIM FPLMN list.
      * This may be less than the number of PLMNs passed in where the SIM file does not have enough
      * room for all of the values passed in. Return -1 in the event of an unexpected failure
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -8637,6 +8928,8 @@
      * @param appType of type int of either {@link #APPTYPE_USIM} or {@link #APPTYPE_ISIM}.
      * @return HexString represents sim service table else null.
      * @throws SecurityException if the caller does not have the required permission/privileges
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
 
@@ -8673,6 +8966,9 @@
      * state.
      *
      * @param slotIndex the sim slot to reset the IMS stack on.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide */
     @SystemApi
     @WorkerThread
@@ -9084,12 +9380,15 @@
      *
      * @return The bitmask of preferred network types.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      * @deprecated Use {@link #getAllowedNetworkTypesBitmask} instead.
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @SystemApi
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public @NetworkTypeBitMask long getPreferredNetworkTypeBitmask() {
         return getAllowedNetworkTypesBitmask();
     }
@@ -9108,6 +9407,8 @@
      *
      * @return The bitmask of allowed network types.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -9133,10 +9434,14 @@
      * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
      * @return the allowed network type bitmask
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      * @deprecated Use {@link #getAllowedNetworkTypesForReason} instead.
      */
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     @SystemApi
     @Deprecated
     public @NetworkTypeBitMask long getAllowedNetworkTypes() {
@@ -9161,6 +9466,9 @@
      * <p>Requires Permission:
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -9247,6 +9555,9 @@
      *        tasks one at a time in serial order.
      * @param callback Returns network scan results or errors.
      * @return A NetworkScan obj which contains a callback which can be used to stop the scan.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(allOf = {
@@ -9295,11 +9606,15 @@
      *        tasks one at a time in serial order.
      * @param callback Returns network scan results or errors.
      * @return A NetworkScan obj which contains a callback which can be used to stop the scan.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(allOf = {
             android.Manifest.permission.MODIFY_PHONE_STATE
     })
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public @Nullable NetworkScan requestNetworkScan(
             @IncludeLocationData int includeLocationData,
             @NonNull NetworkScanRequest request,
@@ -9317,6 +9632,9 @@
     }
 
     /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
+     *
      * @deprecated
      * Use {@link
      * #requestNetworkScan(NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)}
@@ -9327,6 +9645,7 @@
             android.Manifest.permission.MODIFY_PHONE_STATE,
             Manifest.permission.ACCESS_FINE_LOCATION
     })
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public NetworkScan requestNetworkScan(
         NetworkScanRequest request, TelephonyScanManager.NetworkScanCallback callback) {
         return requestNetworkScan(request, AsyncTask.SERIAL_EXECUTOR, callback);
@@ -9347,6 +9666,9 @@
      * attaching to the selected PLMN until reboot; otherwise, attach to the chosen PLMN and resume
      * normal network selection next time.
      * @return {@code true} on success; {@code false} on any failure.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -9378,6 +9700,9 @@
      *         {@link AccessNetworkConstants.AccessNetworkType#UNKNOWN}, modem will select
      *         the next best RAN for network registration.
      * @return {@code true} on success; {@code false} on any failure.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -9430,6 +9755,9 @@
      * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
      * @return the network selection mode.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @SuppressAutoDoc // No support for carrier privileges (b/72967236).
     @RequiresPermission(anyOf = {
@@ -9459,6 +9787,9 @@
      * (see {@link #hasCarrierPrivileges})
      *
      * @return manually selected network info on success or empty string on failure
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @SuppressAutoDoc // No support carrier privileges (b/72967236).
     @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE)
@@ -9488,6 +9819,8 @@
      *
      * @return {@code true} if this device is in emergency SMS mode, {@code false} otherwise.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      * @hide
      */
     @SystemApi
@@ -9557,11 +9890,15 @@
      *
      * @param networkTypeBitmask The bitmask of preferred network types.
      * @return true on success; false on any failure.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      * @deprecated Use {@link #setAllowedNetworkTypesForReason} instead.
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     @SystemApi
     public boolean setPreferredNetworkTypeBitmask(@NetworkTypeBitMask long networkTypeBitmask) {
         try {
@@ -9603,6 +9940,10 @@
      *
      * @param allowedNetworkTypes The bitmask of allowed network types.
      * @return true on success; false on any failure.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
+     *
      * @hide
      * @deprecated Use {@link #setAllowedNetworkTypesForReason} instead with reason
      * {@link #ALLOWED_NETWORK_TYPES_REASON_CARRIER}.
@@ -9690,11 +10031,16 @@
      * @throws IllegalStateException if the Telephony process is not currently available.
      * @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed.
      * @throws SecurityException if the caller does not have the required privileges or if the
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * caller tries to use one of the following security-based reasons without
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE} permissions.
      * <ol>
      *     <li>{@code TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G}</li>
      * </ol>
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(
@@ -9734,6 +10080,8 @@
      * @throws IllegalStateException    if the Telephony process is not currently available.
      * @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed.
      * @throws SecurityException if the caller does not have the required permission/privileges
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @RequiresFeature(
@@ -9808,6 +10156,9 @@
      * <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
      * @return true on success; false on any failure.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public boolean setPreferredNetworkTypeToGlobal() {
@@ -9833,6 +10184,9 @@
      * Requires Permission: MODIFY_PHONE_STATE.
      *
      * @return {@code true} if DUN APN is required for tethering.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -9886,6 +10240,9 @@
      * is a superset of the checks done in SubscriptionManager#canManageSubscription.
      *
      * @return true if the app has carrier privileges.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public boolean hasCarrierPrivileges() {
@@ -9933,6 +10290,9 @@
      *
      * @param brand The brand name to display/set.
      * @return true if the operation was executed correctly.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public boolean setOperatorBrandOverride(String brand) {
@@ -10034,7 +10394,11 @@
      * Expose the rest of ITelephony to @SystemApi
      */
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
+     * @hide
+     */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
@@ -10042,7 +10406,11 @@
         return getCdmaMdn(getSubId());
     }
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
+     * @hide
+     */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
@@ -10059,7 +10427,11 @@
         }
     }
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
+     * @hide
+     */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
@@ -10067,7 +10439,11 @@
         return getCdmaMin(getSubId());
     }
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
+     * @hide
+     */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
@@ -10084,7 +10460,11 @@
         }
     }
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+     * @hide
+     */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -10101,7 +10481,11 @@
         return CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
     }
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+     * @hide
+     */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -10118,14 +10502,22 @@
         return CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
     }
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+     * @hide
+     */
     @SystemApi
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public List<String> getCarrierPackageNamesForIntent(Intent intent) {
         return getCarrierPackageNamesForIntentAndPhone(intent, getPhoneId());
     }
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+     * @hide
+     */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -10152,10 +10544,13 @@
      * @return The system-selected package that provides the {@link CarrierService} implementation
      * for the current subscription, or {@code null} if none is resolved
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public @Nullable String getCarrierServicePackageName() {
         return getCarrierServicePackageNameForLogicalSlot(getPhoneId());
     }
@@ -10169,10 +10564,13 @@
      * @return The system-selected package that provides the {@link CarrierService} implementation
      * for the slot, or {@code null} if none is resolved
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public @Nullable String getCarrierServicePackageNameForLogicalSlot(int logicalSlotIndex) {
         try {
             ITelephony telephony = getITelephony();
@@ -10206,6 +10604,8 @@
     /**
      * Get the names of packages with carrier privileges for all the active subscriptions.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -10256,6 +10656,8 @@
      *
      * @throws IllegalArgumentException if requested state is invalid.
      * @throws SecurityException if the caller does not have the permission.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
@@ -10285,6 +10687,9 @@
      *
      * @return the user-set status for enriched calling with call composer, either of
      * {@link #CALL_COMPOSER_STATUS_ON} or {@link #CALL_COMPOSER_STATUS_OFF}.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
@@ -10301,7 +10706,11 @@
         return CALL_COMPOSER_STATUS_OFF;
     }
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
+     * @hide
+     */
     @SystemApi
     @SuppressLint("RequiresPermission")
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
@@ -10316,6 +10725,9 @@
     }
 
     /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
+     *
      * @deprecated Use  {@link android.telecom.TelecomManager#placeCall(Uri address,
      * Bundle extras)} instead.
      * @hide
@@ -10323,6 +10735,7 @@
     @Deprecated
     @SystemApi
     @RequiresPermission(android.Manifest.permission.CALL_PHONE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public void call(String callingPackage, String number) {
         try {
             ITelephony telephony = getITelephony();
@@ -10369,6 +10782,8 @@
     }
 
     /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELECOM}.
      * @deprecated Use {@link android.telecom.TelecomManager#isInCall} instead
      * @hide
      */
@@ -10378,12 +10793,15 @@
             android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
             android.Manifest.permission.READ_PHONE_STATE
     })
+    @RequiresFeature(PackageManager.FEATURE_TELECOM)
     public boolean isOffhook() {
         TelecomManager tm = (TelecomManager) mContext.getSystemService(TELECOM_SERVICE);
         return tm.isInCall();
     }
 
     /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELECOM}.
      * @deprecated Use {@link android.telecom.TelecomManager#isRinging} instead
      * @hide
      */
@@ -10393,12 +10811,15 @@
             android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
             android.Manifest.permission.READ_PHONE_STATE
     })
+    @RequiresFeature(PackageManager.FEATURE_TELECOM)
     public boolean isRinging() {
         TelecomManager tm = (TelecomManager) mContext.getSystemService(TELECOM_SERVICE);
         return tm.isRinging();
     }
 
     /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELECOM}.
      * @deprecated Use {@link android.telecom.TelecomManager#isInCall} instead
      * @hide
      */
@@ -10408,12 +10829,15 @@
             android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
             android.Manifest.permission.READ_PHONE_STATE
     })
+    @RequiresFeature(PackageManager.FEATURE_TELECOM)
     public boolean isIdle() {
         TelecomManager tm = (TelecomManager) mContext.getSystemService(TELECOM_SERVICE);
         return !tm.isInCall();
     }
 
     /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @deprecated Use {@link android.telephony.TelephonyManager#getServiceState} instead
      * @hide
      */
@@ -10423,6 +10847,7 @@
             android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
             android.Manifest.permission.READ_PHONE_STATE
     })
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public boolean isRadioOn() {
         try {
             ITelephony telephony = getITelephony();
@@ -10434,7 +10859,11 @@
         return false;
     }
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+     * @hide
+     */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -10449,7 +10878,11 @@
         return false;
     }
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+     * @hide
+     */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -10465,11 +10898,15 @@
     }
 
     /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+     *
      * @deprecated use {@link #supplyIccLockPin(String)} instead.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     @Deprecated
     public int[] supplyPinReportResult(String pin) {
         try {
@@ -10483,11 +10920,15 @@
     }
 
     /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
+     *
      * @deprecated use {@link #supplyIccLockPuk(String, String)} instead.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     @Deprecated
     public int[] supplyPukReportResult(String puk, String pin) {
         try {
@@ -10513,6 +10954,8 @@
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -10549,6 +10992,8 @@
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -10622,6 +11067,9 @@
      * @param callback called by the framework to inform the caller of the result of executing the
      *                 USSD request (see {@link UssdResponseCallback}).
      * @param handler the {@link Handler} to run the request on.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @RequiresPermission(android.Manifest.permission.CALL_PHONE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -10666,6 +11114,9 @@
      * voice and data simultaneously. This can change based on location or network condition.
      *
      * @return {@code true} if simultaneous voice and data supported, and {@code false} otherwise.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
     public boolean isConcurrentVoiceAndDataSupported() {
@@ -10679,9 +11130,13 @@
         return false;
     }
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
+     * @hide */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public boolean handlePinMmi(String dialString) {
         try {
             ITelephony telephony = getITelephony();
@@ -10693,9 +11148,14 @@
         return false;
     }
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
+     * @hide
+     */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public boolean handlePinMmiForSubscriber(int subId, String dialString) {
         try {
             ITelephony telephony = getITelephony();
@@ -10707,7 +11167,11 @@
         return false;
     }
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
+     * @hide
+     */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -10725,6 +11189,8 @@
      * @deprecated - use the APIs {@link requestRadioPowerOffForReason} and
      * {@link clearRadioPowerOffForReason}.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @Deprecated
@@ -10752,6 +11218,8 @@
      * @deprecated - use the APIs {@link requestRadioPowerOffForReason} and
      * {@link clearRadioPowerOffForReason}.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @Deprecated
@@ -10799,6 +11267,8 @@
      * @throws SecurityException if the caller does not have MODIFY_PHONE_STATE permission.
      * @throws IllegalStateException if the Telephony service is not currently available.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @SystemApi
@@ -10828,6 +11298,8 @@
      * @throws SecurityException if the caller does not have MODIFY_PHONE_STATE permission.
      * @throws IllegalStateException if the Telephony service is not currently available.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @SystemApi
@@ -10857,6 +11329,8 @@
      * @throws SecurityException if the caller does not have READ_PRIVILEGED_PHONE_STATE permission.
      * @throws IllegalStateException if the Telephony service is not currently available.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @SystemApi
@@ -10886,6 +11360,8 @@
      * <p>To know when the radio has completed powering off, use
      * {@link PhoneStateListener#LISTEN_SERVICE_STATE LISTEN_SERVICE_STATE}.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @SystemApi
@@ -10907,6 +11383,9 @@
      * Check if any radio is on over all the slot indexes.
      *
      * @return {@code true} if any radio is on over any slot index.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @SystemApi
@@ -10953,6 +11432,8 @@
      * {@link android.Manifest.permission#READ_PHONE_STATE} or that the calling
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @SystemApi
@@ -10982,7 +11463,11 @@
         Log.e(TAG, "Do not call TelephonyManager#updateServiceLocation()");
     }
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
+     * @hide
+     */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
@@ -10997,7 +11482,11 @@
         return false;
     }
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
+     * @hide
+     */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
@@ -11012,7 +11501,11 @@
         return false;
     }
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
+     * @hide
+     */
     @SystemApi
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
     public boolean isDataConnectivityPossible() {
@@ -11027,7 +11520,11 @@
         return false;
     }
 
-    /** @hide */
+    /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
+     * @hide
+     */
     @SystemApi
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public boolean needsOtaServiceProvisioning() {
@@ -11078,23 +11575,29 @@
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
      * @param enable Whether to enable mobile data.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      * @deprecated use setDataEnabledForReason with reason DATA_ENABLED_REASON_USER instead.
      *
      */
     @Deprecated
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
     public void setDataEnabled(boolean enable) {
         setDataEnabled(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()), enable);
     }
 
     /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      * @hide
      * @deprecated use {@link #setDataEnabledForReason(int, boolean)} instead.
     */
     @SystemApi
     @Deprecated
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
     public void setDataEnabled(int subId, boolean enable) {
         try {
             setDataEnabledForReason(subId, DATA_ENABLED_REASON_USER, enable);
@@ -11105,10 +11608,14 @@
 
     /**
      * @deprecated use {@link #isDataEnabled()} instead.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      * @hide
      */
     @SystemApi
     @Deprecated
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
     public boolean getDataEnabled() {
         return isDataEnabled();
     }
@@ -11132,6 +11639,9 @@
      * {@link ConnectivityManager#getRestrictBackgroundStatus}.
      *
      * @return true if mobile data is enabled.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      */
     @RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE,
             android.Manifest.permission.MODIFY_PHONE_STATE,
@@ -11162,6 +11672,9 @@
      * has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
      * @return {@code true} if the data roaming is enabled on the subscription, otherwise return
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      * {@code false}.
      */
     @RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE,
@@ -11201,6 +11714,8 @@
      * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
      * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
      * @hide
      */
     @SystemApi
@@ -11243,6 +11758,8 @@
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
      * @hide
      */
     @SystemApi
@@ -11311,6 +11828,8 @@
      * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
      * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
      * @hide
      */
     @SystemApi
@@ -11349,6 +11868,8 @@
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
      * @hide
      */
     @SystemApi
@@ -11384,6 +11905,8 @@
      *
      * @param isEnabled {@code true} to enable mobile data roaming, otherwise disable it.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      * @hide
      */
     @SystemApi
@@ -11402,11 +11925,15 @@
     }
 
     /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
+     *
      * @deprecated use {@link #isDataEnabled()} instead.
      * @hide
      */
     @Deprecated
     @SystemApi
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
     public boolean getDataEnabled(int subId) {
         try {
             return isDataEnabledForReason(subId, DATA_ENABLED_REASON_USER);
@@ -11417,6 +11944,8 @@
     }
 
     /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @deprecated Use {@link android.telephony.ims.ImsMmTelManager#setVtSettingEnabled(boolean)}
      * instead.
      * @hide
@@ -11424,6 +11953,7 @@
     @Deprecated
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS)
     public void enableVideoCalling(boolean enable) {
         try {
             ITelephony telephony = getITelephony();
@@ -11435,6 +11965,8 @@
     }
 
     /**
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @deprecated Use {@link ImsMmTelManager#isVtSettingEnabled()} instead to check if the user
      * has enabled the Video Calling setting, {@link ImsMmTelManager#isAvailable(int, int)} to
      * determine if video calling is available, or {@link ImsMmTelManager#isCapable(int, int)} to
@@ -11447,6 +11979,7 @@
             android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
             android.Manifest.permission.READ_PHONE_STATE
     })
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS)
     public boolean isVideoCallingEnabled() {
         try {
             ITelephony telephony = getITelephony();
@@ -11462,6 +11995,9 @@
      * Whether the device supports configuring the DTMF tone length.
      *
      * @return {@code true} if the DTMF tone length can be changed, and {@code false} otherwise.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public boolean canChangeDtmfToneLength() {
@@ -11483,7 +12019,11 @@
      * Whether the device is a world phone.
      *
      * @return {@code true} if the device is a world phone, and {@code false} otherwise.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
     public boolean isWorldPhone() {
         try {
             ITelephony telephony = getITelephony();
@@ -11504,8 +12044,11 @@
      *
      * @return {@code true} if the device supports TTY mode, and {@code false} otherwise.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELECOM}.
      */
     @Deprecated
+    @RequiresFeature(PackageManager.FEATURE_TELECOM)
     public boolean isTtyModeSupported() {
         try {
             TelecomManager telecomManager = null;
@@ -11526,6 +12069,9 @@
      * support for the feature and device firmware support.
      *
      * @return {@code true} if the device and carrier both support RTT, {@code false} otherwise.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS)
     public boolean isRttSupported() {
@@ -11546,6 +12092,9 @@
      *
      * @return {@code true} if the device supports hearing aid compatibility, and {@code false}
      * otherwise.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public boolean isHearingAidCompatibilitySupported() {
@@ -11808,11 +12357,14 @@
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * {@hide}
      **/
     @SystemApi
     @Deprecated
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public void setSimPowerState(int state) {
         setSimPowerStateForSlot(getSlotIndex(), state);
     }
@@ -11834,11 +12386,14 @@
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * {@hide}
      **/
     @SystemApi
     @Deprecated
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public void setSimPowerStateForSlot(int slotIndex, int state) {
         try {
             ITelephony telephony = getITelephony();
@@ -11871,6 +12426,8 @@
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * {@hide}
      **/
     @SystemApi
@@ -11901,6 +12458,8 @@
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * {@hide}
      **/
     @SystemApi
@@ -12055,6 +12614,9 @@
      * application currently configured for this user.
      * @return component name of the app and class to direct Respond Via Message intent to, or
      * {@code null} if the functionality is not supported.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      * @hide
      */
     @SystemApi
@@ -12077,6 +12639,9 @@
      * user associated with this subscription.
      * @return component name of the app and class to direct Respond Via Message intent to, or
      * {@code null} if the functionality is not supported.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      * @hide
      */
     @SystemApi
@@ -12213,9 +12778,13 @@
      * @return The {@link PhoneAccountHandle} associated with the TelphonyManager, or {@code null}
      * if there is no associated {@link PhoneAccountHandle}; this can happen if the subscription is
      * data-only or an opportunistic subscription.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public @Nullable PhoneAccountHandle getPhoneAccountHandle() {
         return getPhoneAccountHandleForSubscriptionId(getSubId());
     }
@@ -12277,10 +12846,14 @@
      * Resets Telephony and IMS settings back to factory defaults only for the subscription
      * associated with this instance.
      * @see #createForSubscriptionId(int)
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.CONNECTIVITY_INTERNAL)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
     public void resetSettings() {
         try {
             Log.d(TAG, "resetSettings: subId=" + getSubId());
@@ -12302,6 +12875,8 @@
      *
      * @see Locale#toLanguageTag()
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -12394,10 +12969,14 @@
      * @param callback A callback object to which the result will be delivered. If there was an
      *                 error processing the request, {@link OutcomeReceiver#onError} will be called
      *                 with more details about the error.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
     public void requestModemActivityInfo(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<ModemActivityInfo, ModemActivityInfoException> callback) {
         Objects.requireNonNull(executor);
@@ -12484,6 +13063,9 @@
      * and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
      * May return {@code null} when the subscription is inactive or when there was an error
      * communicating with the phone process.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(allOf = {
@@ -12516,12 +13098,16 @@
      * location related information.
      * May return {@code null} when the subscription is inactive or when there was an error
      * communicating with the phone process.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(allOf = {
             Manifest.permission.READ_PHONE_STATE,
             Manifest.permission.ACCESS_COARSE_LOCATION
     })
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public @Nullable ServiceState getServiceState(@IncludeLocationData int includeLocationData) {
         return getServiceStateForSubscriber(getSubId(),
                 includeLocationData != INCLUDE_LOCATION_DATA_FINE,
@@ -12580,6 +13166,9 @@
      * voicemail ringtone.
      * @return The URI for the ringtone to play when receiving a voicemail from a specific
      * PhoneAccount. May be {@code null} if no ringtone is set.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public @Nullable Uri getVoicemailRingtoneUri(PhoneAccountHandle accountHandle) {
@@ -12606,10 +13195,13 @@
      * @param uri The URI for the ringtone to play when receiving a voicemail from a specific
      * PhoneAccount.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      * @deprecated Use {@link android.provider.Settings#ACTION_CHANNEL_NOTIFICATION_SETTINGS}
      * instead.
      */
     @Deprecated
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public void setVoicemailRingtoneUri(PhoneAccountHandle phoneAccountHandle, Uri uri) {
         try {
             ITelephony service = getITelephony();
@@ -12627,6 +13219,9 @@
      * @param accountHandle The handle for the {@link PhoneAccount} for which to retrieve the
      * voicemail vibration setting.
      * @return {@code true} if the vibration is set for this PhoneAccount, {@code false} otherwise.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public boolean isVoicemailVibrationEnabled(PhoneAccountHandle accountHandle) {
@@ -12653,10 +13248,13 @@
      * @param enabled Whether to enable or disable vibration for voicemail notifications from a
      * specific PhoneAccount.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      * @deprecated Use {@link android.provider.Settings#ACTION_CHANNEL_NOTIFICATION_SETTINGS}
      * instead.
      */
     @Deprecated
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public void setVoicemailVibrationEnabled(PhoneAccountHandle phoneAccountHandle,
             boolean enabled) {
         try {
@@ -12682,6 +13280,9 @@
      *
      * @return Carrier id of the current subscription. Return {@link #UNKNOWN_CARRIER_ID} if the
      * subscription is unavailable or the carrier cannot be identified.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public int getSimCarrierId() {
@@ -12707,6 +13308,9 @@
      *
      * @return Carrier name of the current subscription. Return {@code null} if the subscription is
      * unavailable or the carrier cannot be identified.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public @Nullable CharSequence getSimCarrierIdName() {
@@ -12745,6 +13349,9 @@
      * @return Returns fine-grained carrier id of the current subscription.
      * Return {@link #UNKNOWN_CARRIER_ID} if the subscription is unavailable or the carrier cannot
      * be identified.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public int getSimSpecificCarrierId() {
@@ -12771,6 +13378,9 @@
      *
      * @return user-facing name of the subscription specific carrier id. Return {@code null} if the
      * subscription is unavailable or the carrier cannot be identified.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public @Nullable CharSequence getSimSpecificCarrierIdName() {
@@ -12799,6 +13409,9 @@
      *
      * @return matching carrier id from sim MCCMNC. Return {@link #UNKNOWN_CARRIER_ID} if the
      * subscription is unavailable or the carrier cannot be identified.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public int getCarrierIdFromSimMccMnc() {
@@ -12874,6 +13487,9 @@
      *
      * @param appType the uicc app type.
      * @return Application ID for specified app type or {@code null} if no uicc or error.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @Nullable
@@ -12939,6 +13555,9 @@
      * Requires that the calling app has READ_PRIVILEGED_PHONE_STATE permission
      *
      * @return PRLVersion or null if error.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CDMA}.
      * @hide
      */
     @SystemApi
@@ -13004,6 +13623,9 @@
      *
      * @return The number of carriers set successfully. Should be length of
      * carrierList on success; -1 if carrierList null or on error.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CARRIERLOCK}.
      * @hide
      */
     @SystemApi
@@ -13130,6 +13752,9 @@
      * @return {@link #SET_CARRIER_RESTRICTION_SUCCESS} in case of success.
      * {@link #SET_CARRIER_RESTRICTION_NOT_SUPPORTED} if the modem does not support the
      * configuration. {@link #SET_CARRIER_RESTRICTION_ERROR} in all other error cases.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CARRIERLOCK}.
      * @hide
      */
     @SystemApi
@@ -13163,11 +13788,15 @@
      *
      * @return List of {@link android.telephony.CarrierIdentifier}; empty list
      * means all carriers are allowed.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CARRIERLOCK}.
      * @hide
      */
     @Deprecated
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CARRIERLOCK)
     public List<CarrierIdentifier> getAllowedCarriers(int slotIndex) {
         if (SubscriptionManager.isValidPhoneId(slotIndex)) {
             CarrierRestrictionRules carrierRestrictionRule = getCarrierRestrictionRules();
@@ -13189,6 +13818,9 @@
      * @return {@link CarrierRestrictionRules} which contains the allowed carrier list and the
      * excluded carrier list with the priority between the two lists. Returns {@code null}
      * in case of error.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CARRIERLOCK}.
      * @hide
      */
     @SystemApi
@@ -13257,6 +13889,8 @@
      *                       status result fetched from the radio
      * @throws SecurityException if the caller does not have the required permission/privileges or
      *                           if the caller is not pre-registered.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -13323,11 +13957,15 @@
      * @see #resetAllCarrierActions()
      * @deprecated use {@link #setDataEnabledForReason(int, boolean) with
      * reason {@link #DATA_ENABLED_REASON_CARRIER}} instead.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      * @hide
      */
     @Deprecated
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
     public void setCarrierDataEnabled(boolean enabled) {
         try {
             setDataEnabledForReason(DATA_ENABLED_REASON_CARRIER, enabled);
@@ -13351,6 +13989,8 @@
      * @deprecated - use the APIs {@link requestRadioPowerOffForReason} and
      * {@link clearRadioPowerOffForReason}.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @Deprecated
@@ -13470,6 +14110,9 @@
      *
      * @param report control start/stop reporting network status.
      * @see #resetAllCarrierActions()
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @SystemApi
@@ -13496,6 +14139,8 @@
      *
      * <p>Requires Permission:
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -13618,6 +14263,8 @@
      * has {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} irrespective of
      * the reason.
      * @throws IllegalStateException if the Telephony process is not currently available.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
@@ -13661,6 +14308,8 @@
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE}
      * {@link android.Manifest.permission#READ_BASIC_PHONE_STATE}
      * @throws IllegalStateException if the Telephony process is not currently available.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      */
     @RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE,
             android.Manifest.permission.READ_PHONE_STATE,
@@ -13717,6 +14366,9 @@
      * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
      *
      * @return true if phone is in emergency callback mode.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      * @hide
      */
     @SystemApi
@@ -13756,6 +14408,9 @@
      * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}.
      *
      * @return {@code true} if manual network selection is allowed, otherwise return {@code false}.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @SuppressAutoDoc // No support carrier privileges (b/72967236).
     @RequiresPermission(anyOf = {android.Manifest.permission.READ_PRECISE_PHONE_STATE,
@@ -13779,6 +14434,9 @@
      * Get the most recent SignalStrength information reported by the modem. Due
      * to power saving this information may not always be current.
      * @return the most recent cached signal strength info from the modem
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @Nullable
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -13805,6 +14463,9 @@
      *   <LI>And possibly others.</LI>
      * </UL>
      * @return {@code true} if the overall data connection is allowed; {@code false} if not.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      */
     @RequiresPermission(anyOf = {android.Manifest.permission.ACCESS_NETWORK_STATE,
             android.Manifest.permission.READ_PHONE_STATE,
@@ -13975,6 +14636,9 @@
      *
      * @param enable enable(True) or disable(False)
      * @return returns true if successfully set.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -14004,6 +14668,9 @@
      * <p>
      * Requires Permission:
      *   {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -14205,6 +14872,8 @@
      * that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
      * @throws SecurityException if the caller does not have the required permission
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -14240,6 +14909,8 @@
      * <p> Requires permission:
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -14267,6 +14938,8 @@
      * <p> Requires permission:
      * {@link android.Manifest.permission#READ_ACTIVE_EMERGENCY_SESSION}
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
@@ -14293,6 +14966,8 @@
      * <p> Requires permission:
      * {@link android.Manifest.permission#READ_ACTIVE_EMERGENCY_SESSION}
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
@@ -14353,6 +15028,9 @@
      * subscription, the key is {@link SubscriptionManager#getDefaultSubscriptionId}) and the value
      * as the list of {@link EmergencyNumber}; empty Map if this information is not available;
      * or throw a SecurityException if the caller does not have the permission.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     @NonNull
@@ -14409,6 +15087,8 @@
      * as the list of {@link EmergencyNumber}; empty Map if this information is not available;
      * or throw a SecurityException if the caller does not have the permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     @NonNull
@@ -14477,6 +15157,8 @@
      * @return {@code true} if the given number is an emergency number based on current locale,
      * SIM card(s), Android database, modem, network or defaults; {@code false} otherwise.
      * @throws IllegalStateException if the Telephony process is not currently available.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public boolean isEmergencyNumber(@NonNull String number) {
@@ -14514,6 +15196,8 @@
      * network; {@code false} if it is not; or throw an SecurityException if the caller does not
      * have the required permission/privileges
      * @throws IllegalStateException if the Telephony process is not currently available.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      *
      * @deprecated Please use {@link TelephonyManager#isEmergencyNumber(String)} instead.
      * @hide
@@ -14543,6 +15227,8 @@
      * <p>Requires Permission:
      *   {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      * @hide
      */
     @SystemApi
@@ -14694,6 +15380,9 @@
      * @param callback Callback will be triggered once it succeeds or failed.
      *                 See the {@code SET_OPPORTUNISTIC_SUB_*} constants
      *                 for more details. Pass null if don't care about the result.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
     public void setPreferredOpportunisticDataSubscription(int subId, boolean needValidation,
@@ -14754,6 +15443,8 @@
      * {@link SubscriptionManager#DEFAULT_SUBSCRIPTION_ID} if there are no preferred
      * subscription id
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      */
     @RequiresPermission(anyOf = {
             android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
@@ -14793,6 +15484,8 @@
      * @param executor The executor of where the callback will execute.
      * @param callback Callback will be triggered once it succeeds or failed.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
@@ -14853,10 +15546,13 @@
      * @param enable whether to enable or disable the modem stack.
      * @return whether the operation is successful.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
     public boolean enableModemForSlot(int slotIndex, boolean enable) {
         boolean ret = false;
         try {
@@ -14879,10 +15575,14 @@
      * {@link #hasCarrierPrivileges()}).
      *
      * @param slotIndex which slot it's checking.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(anyOf = {android.Manifest.permission.READ_PHONE_STATE,
             android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE})
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY)
     public boolean isModemEnabledForSlot(int slotIndex) {
         try {
             ITelephony telephony = getITelephony();
@@ -14945,6 +15645,8 @@
      * @param isMultiSimCarrierRestricted true if usage of multiple SIMs is restricted, false
      * otherwise.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CARRIERLOCK}.
      * @hide
      */
     @SystemApi
@@ -15000,6 +15702,9 @@
      * {@link #MULTISIM_NOT_SUPPORTED_BY_HARDWARE} if the device does not support multiple SIMs.
      * {@link #MULTISIM_NOT_SUPPORTED_BY_CARRIER} in the device supports multiple SIMs, but the
      * functionality is restricted by the carrier.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -15031,6 +15736,8 @@
      *
      * @param numOfSims number of live SIMs we want to switch to
      * @throws android.os.RemoteException
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -15058,6 +15765,9 @@
      *
      * @return {@code true} if reboot will be triggered after making changes to modem
      * configurations, otherwise return {@code false}.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
@@ -15220,6 +15930,8 @@
      *         {@link #CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED}, or
      *         {@link #CARRIER_PRIVILEGE_STATUS_ERROR_LOADING_RULES}
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -15334,6 +16046,8 @@
      * @param apnType Value indicating the apn type. Apn types are defined in {@link ApnSetting}.
      * @return whether data is enabled for a apn type.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      * @hide
      */
     @SystemApi
@@ -15356,6 +16070,8 @@
      * Whether an APN type is metered or not. It will be evaluated with the subId associated
      * with the TelephonyManager instance.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      * @hide
      */
     @SystemApi
@@ -15385,6 +16101,9 @@
      * @param executor The executor to execute the callback on
      * @param callback The callback that gets invoked when the radio responds to the request. Called
      *                 with {@code true} if the request succeeded, {@code false} otherwise.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @SystemApi
@@ -15403,6 +16122,9 @@
      * Same as {@link #setSystemSelectionChannels(List, Executor, Consumer<Boolean>)}, but to be
      * used when the caller does not need feedback on the results of the operation.
      * @param specifiers which bands to scan.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @SystemApi
@@ -15450,6 +16172,8 @@
      * @return a list of {@link RadioAccessSpecifier}, or an empty list if no bands are specified.
      * @throws IllegalStateException if the Telephony process is not currently available.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @SystemApi
@@ -15478,6 +16202,8 @@
      * @return {@code true} if input mccmnc and mvno matches with data from sim operator.
      * {@code false} otherwise.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * {@hide}
      */
     @SystemApi
@@ -15568,6 +16294,8 @@
      * {@link CallForwardingInfo#REASON_UNCONDITIONAL}, {@link CallForwardingInfo#REASON_BUSY},
      * {@link CallForwardingInfo#REASON_NO_REPLY}, {@link CallForwardingInfo#REASON_NOT_REACHABLE},
      * {@link CallForwardingInfo#REASON_ALL}, or {@link CallForwardingInfo#REASON_ALL_CONDITIONAL}
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      *
      * @hide
      */
@@ -15645,6 +16373,8 @@
      * <li>{@link CallForwardingInfo#getTimeoutSeconds()} returns a non-positive value when
      * enabling call forwarding</li>
      * </ul>
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -15769,6 +16499,9 @@
      *                          <li>{@link #CALL_WAITING_STATUS_NOT_SUPPORTED}}</li>
      *                          <li>{@link #CALL_WAITING_STATUS_FDN_CHECK_FAILURE}}</li>
      *                       </ul>
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      * @hide
      */
     @SystemApi
@@ -15819,6 +16552,9 @@
      *                       {@link #CALL_WAITING_STATUS_NOT_SUPPORTED} or
      *                       {@link #CALL_WAITING_STATUS_UNKNOWN_ERROR} or
      *                       {@link #CALL_WAITING_STATUS_FDN_CHECK_FAILURE} if it failed.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_CALLING}.
      * @hide
      */
     @SystemApi
@@ -15919,6 +16655,9 @@
      *
      * @param policy The data policy to enable.
      * @param enabled Whether to enable or disable the policy.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      * @hide
      */
     @SystemApi
@@ -15940,6 +16679,9 @@
      *
      * @param policy The data policy that you want the status for.
      * @return {@code true} if enabled, {@code false} otherwise.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      * @hide
      */
     @SystemApi
@@ -15975,6 +16717,8 @@
      * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE}
      * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @WorkerThread
@@ -16009,6 +16753,8 @@
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -16051,6 +16797,8 @@
      * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling
      * app has carrier privileges (see {@link #hasCarrierPrivileges}).
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -16199,6 +16947,8 @@
      * </ol>
      * @return operation result.
      * @throws IllegalStateException if the Telephony process is not currently available.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @SystemApi
@@ -16233,6 +16983,8 @@
      * connectivity is active. It means the device is allowed to connect to both primary and
      * secondary cell.
      * @throws IllegalStateException if the Telephony process is not currently available.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @SystemApi
@@ -16470,6 +17222,8 @@
      *
      * @throws IllegalStateException if the Telephony process is not currently available.
      * @throws SecurityException if the caller doesn't have the permission.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      *
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -16586,6 +17340,9 @@
      *
      * @param capability the name of the capability to check for
      * @return the availability of the capability
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
     public boolean isRadioInterfaceCapabilitySupported(
@@ -16705,6 +17462,8 @@
      * @throws IllegalArgumentException if the thermalMitigationRequest had invalid parameters or
      * if the device's modem does not support data throttling.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      * @hide
      */
     @SystemApi
@@ -17037,6 +17796,9 @@
      * contain the GBA Ks_NAF/Ks_ext_NAF when available. If the NAF keys are
      * available and valid at the time of call and bootstrapping is not requested,
      * then the callback shall be invoked with the available keys.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -17135,6 +17897,8 @@
      * @param request the SignalStrengthUpdateRequest to be set into the System
      *
      * @throws IllegalStateException if a new request is set with same subId from the same caller
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -17165,6 +17929,9 @@
      * @see #setSignalStrengthUpdateRequest(SignalStrengthUpdateRequest)
      *
      * @param request the SignalStrengthUpdateRequest to be cleared from the System
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -17188,10 +17955,14 @@
      * @return the PhoneCapability which describes the data connection capability of modem.
      * It's used to evaluate possible phone config change, for example from single
      * SIM device to multi-SIM device.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public @NonNull PhoneCapability getPhoneCapability() {
         try {
             ITelephony telephony = getITelephony();
@@ -17256,11 +18027,15 @@
      * at least one SIM card for which the user needs to manually enter the PIN
      * code after the reboot. {@link #PREPARE_UNATTENDED_REBOOT_ERROR} in case
      * of error.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.REBOOT)
     @PrepareUnattendedRebootResult
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public int prepareForUnattendedReboot() {
         try {
             ITelephony service = getITelephony();
@@ -17363,6 +18138,9 @@
      *
      * @param executor the executor on which callback will be invoked.
      * @param callback a callback to receive the current slicing configuration.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_RADIO_ACCESS}.
      */
     @RequiresFeature(
             enforcement = "android.telephony.TelephonyManager#isRadioInterfaceCapabilitySupported",
@@ -17444,8 +18222,11 @@
      * @param capability The premium capability to check.
      * @return Whether the given premium capability is available to purchase.
      * @throws SecurityException if the caller does not hold permission READ_BASIC_PHONE_STATE.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      */
     @RequiresPermission(android.Manifest.permission.READ_BASIC_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
     public boolean isPremiumCapabilityAvailableForPurchase(@PremiumCapability int capability) {
         try {
             ITelephony telephony = getITelephony();
@@ -17685,10 +18466,13 @@
      * @param callback The result of the purchase request.
      * @throws SecurityException if the caller does not hold permissions
      *         READ_BASIC_PHONE_STATE or INTERNET.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_DATA}.
      * @see #isPremiumCapabilityAvailableForPurchase(int) to check whether the capability is valid.
      */
     @RequiresPermission(allOf = {android.Manifest.permission.READ_BASIC_PHONE_STATE,
             android.Manifest.permission.INTERNET})
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_DATA)
     public void purchasePremiumCapability(@PremiumCapability int capability,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull @PurchasePremiumCapabilityResult Consumer<Integer> callback) {
@@ -18142,10 +18926,14 @@
      * Get current cell broadcast message identifier ranges.
      *
      * @throws SecurityException if the caller does not have the required permission
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
+     *
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     @NonNull
     public List<CellBroadcastIdRange> getCellBroadcastIdRanges() {
         try {
@@ -18299,10 +19087,13 @@
      * the result when the operation completes.
      * @throws SecurityException if the caller does not have the required permission
      * @throws IllegalArgumentException when the ranges are invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public void setCellBroadcastIdRanges(@NonNull List<CellBroadcastIdRange> ranges,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<Integer> callback) {
@@ -18378,7 +19169,8 @@
      * </ul>
      *
      * @return Primary IMEI of type string
-     * @throws UnsupportedOperationException if the radio doesn't support this feature.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_GSM}.
      * @throws SecurityException if the caller does not have the required permission/privileges
      */
     @NonNull
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index 9c33576..bbd4567 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -36,12 +36,12 @@
 import android.view.InputDevice
 import android.view.inputmethod.InputMethodInfo
 import android.view.inputmethod.InputMethodSubtype
-import androidx.test.core.R
 import androidx.test.core.app.ApplicationProvider
 import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.internal.os.KeyboardConfiguredProto
 import com.android.internal.util.FrameworkStatsLog
 import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.test.input.R
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotEquals