Merge "Move TYPE_WINDOW_CONTROL toString flag check" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index bfddf4f..a1561c2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6833,6 +6833,16 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR;
   }
 
+  public static final class SoundTrigger.RecognitionConfig.Builder {
+    ctor public SoundTrigger.RecognitionConfig.Builder();
+    method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig build();
+    method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAllowMultipleTriggers(boolean);
+    method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAudioCapabilities(int);
+    method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setCaptureRequested(boolean);
+    method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setData(@Nullable byte[]);
+    method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setKeyphrases(@NonNull java.util.Collection<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
+  }
+
   public static class SoundTrigger.RecognitionEvent {
     method @Nullable public android.media.AudioFormat getCaptureFormat();
     method public int getCaptureSession();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index cc9e836..6511c21 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1887,7 +1887,7 @@
   }
 
   @FlaggedApi("android.media.soundtrigger.manager_api") public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable {
-    ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int);
+    ctor @Deprecated public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int);
     ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]);
   }
 
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 0e761fc..c17da24 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -54,198 +54,8 @@
  * LRU cache that's invalidated when an opaque value in a property changes. Self-synchronizing,
  * but doesn't hold a lock across data fetches on query misses.
  *
- * The intended use case is caching frequently-read, seldom-changed information normally
- * retrieved across interprocess communication. Imagine that you've written a user birthday
- * information daemon called "birthdayd" that exposes an {@code IUserBirthdayService} interface
- * over binder. That binder interface looks something like this:
- *
- * <pre>
- * parcelable Birthday {
- *   int month;
- *   int day;
- * }
- * interface IUserBirthdayService {
- *   Birthday getUserBirthday(int userId);
- * }
- * </pre>
- *
- * Suppose the service implementation itself looks like this...
- *
- * <pre>
- * public class UserBirthdayServiceImpl implements IUserBirthdayService {
- *   private final HashMap&lt;Integer, Birthday%&gt; mUidToBirthday;
- *   {@literal @}Override
- *   public synchronized Birthday getUserBirthday(int userId) {
- *     return mUidToBirthday.get(userId);
- *   }
- *   private synchronized void updateBirthdays(Map&lt;Integer, Birthday%&gt; uidToBirthday) {
- *     mUidToBirthday.clear();
- *     mUidToBirthday.putAll(uidToBirthday);
- *   }
- * }
- * </pre>
- *
- * ... and we have a client in frameworks (loaded into every app process) that looks
- * like this:
- *
- * <pre>
- * public class ActivityThread {
- *   ...
- *   public Birthday getUserBirthday(int userId) {
- *     return GetService("birthdayd").getUserBirthday(userId);
- *   }
- *   ...
- * }
- * </pre>
- *
- * With this code, every time an app calls {@code getUserBirthday(uid)}, we make a binder call
- * to the birthdayd process and consult its database of birthdays. If we query user birthdays
- * frequently, we do a lot of work that we don't have to do, since user birthdays
- * change infrequently.
- *
- * PropertyInvalidatedCache is part of a pattern for optimizing this kind of
- * information-querying code. Using {@code PropertyInvalidatedCache}, you'd write the client
- * this way:
- *
- * <pre>
- * public class ActivityThread {
- *   ...
- *   private final PropertyInvalidatedCache.QueryHandler&lt;Integer, Birthday&gt; mBirthdayQuery =
- *       new PropertyInvalidatedCache.QueryHandler&lt;Integer, Birthday&gt;() {
- *           {@literal @}Override
- *           public Birthday apply(Integer) {
- *              return GetService("birthdayd").getUserBirthday(userId);
- *           }
- *       };
- *   private static final int BDAY_CACHE_MAX = 8;  // Maximum birthdays to cache
- *   private static final String BDAY_CACHE_KEY = "cache_key.birthdayd";
- *   private final PropertyInvalidatedCache&lt;Integer, Birthday%&gt; mBirthdayCache = new
- *     PropertyInvalidatedCache&lt;Integer, Birthday%&gt;(
- *             BDAY_CACHE_MAX, MODULE_SYSTEM, "getUserBirthday", mBirthdayQuery);
- *
- *   public void disableUserBirthdayCache() {
- *     mBirthdayCache.disableForCurrentProcess();
- *   }
- *   public void invalidateUserBirthdayCache() {
- *     mBirthdayCache.invalidateCache();
- *   }
- *   public Birthday getUserBirthday(int userId) {
- *     return mBirthdayCache.query(userId);
- *   }
- *   ...
- * }
- * </pre>
- *
- * With this cache, clients perform a binder call to birthdayd if asking for a user's birthday
- * for the first time; on subsequent queries, we return the already-known Birthday object.
- *
- * The second parameter to the IpcDataCache constructor is a string that identifies the "module"
- * that owns the cache. There are some well-known modules (such as {@code MODULE_SYSTEM} but any
- * string is permitted.  The third parameters is the name of the API being cached; this, too, can
- * any value.  The fourth is the name of the cache.  The cache is usually named after th API.
- * Some things you must know about the three strings:
- * <list>
- * <ul> The system property that controls the cache is named {@code cache_key.<module>.<api>}.
- * Usually, the SELinux rules permit a process to write a system property (and therefore
- * invalidate a cache) based on the wildcard {@code cache_key.<module>.*}.  This means that
- * although the cache can be constructed with any module string, whatever string is chosen must be
- * consistent with the SELinux configuration.
- * <ul> The API name can be any string of alphanumeric characters.  All caches with the same API
- * are invalidated at the same time.  If a server supports several caches and all are invalidated
- * in common, then it is most efficient to assign the same API string to every cache.
- * <ul> The cache name can be any string.  In debug output, the name is used to distiguish between
- * caches with the same API name.  The cache name is also used when disabling caches in the
- * current process.  So, invalidation is based on the module+api but disabling (which is generally
- * a once-per-process operation) is based on the cache name.
- * </list>
- *
- * User birthdays do occasionally change, so we have to modify the server to invalidate this
- * cache when necessary. That invalidation code looks like this:
- *
- * <pre>
- * public class UserBirthdayServiceImpl {
- *   ...
- *   public UserBirthdayServiceImpl() {
- *     ...
- *     ActivityThread.currentActivityThread().disableUserBirthdayCache();
- *     ActivityThread.currentActivityThread().invalidateUserBirthdayCache();
- *   }
- *
- *   private synchronized void updateBirthdays(Map&lt;Integer, Birthday%&gt; uidToBirthday) {
- *     mUidToBirthday.clear();
- *     mUidToBirthday.putAll(uidToBirthday);
- *     ActivityThread.currentActivityThread().invalidateUserBirthdayCache();
- *   }
- *   ...
- * }
- * </pre>
- *
- * The call to {@code PropertyInvalidatedCache.invalidateCache()} guarantees that all clients
- * will re-fetch birthdays from binder during consequent calls to
- * {@code ActivityThread.getUserBirthday()}. Because the invalidate call happens with the lock
- * held, we maintain consistency between different client views of the birthday state. The use
- * of PropertyInvalidatedCache in this idiomatic way introduces no new race conditions.
- *
- * PropertyInvalidatedCache has a few other features for doing things like incremental
- * enhancement of cached values and invalidation of multiple caches (that all share the same
- * property key) at once.
- *
- * {@code BDAY_CACHE_KEY} is the name of a property that we set to an opaque unique value each
- * time we update the cache. SELinux configuration must allow everyone to read this property
- * and it must allow any process that needs to invalidate the cache (here, birthdayd) to write
- * the property. (These properties conventionally begin with the "cache_key." prefix.)
- *
- * The {@code UserBirthdayServiceImpl} constructor calls {@code disableUserBirthdayCache()} so
- * that calls to {@code getUserBirthday} from inside birthdayd don't go through the cache. In
- * this local case, there's no IPC, so use of the cache is (depending on exact
- * circumstance) unnecessary.
- *
- * There may be queries for which it is more efficient to bypass the cache than to cache
- * the result.  This would be true, for example, if some queries would require frequent
- * cache invalidation while other queries require infrequent invalidation.  To expand on
- * the birthday example, suppose that there is a userId that signifies "the next
- * birthday".  When passed this userId, the server returns the next birthday among all
- * users - this value changes as time advances.  The userId value can be cached, but the
- * cache must be invalidated whenever a birthday occurs, and this invalidates all
- * birthdays.  If there is a large number of users, invalidation will happen so often that
- * the cache provides no value.
- *
- * The class provides a bypass mechanism to handle this situation.
- * <pre>
- * public class ActivityThread {
- *   ...
- *   private final IpcDataCache.QueryHandler&lt;Integer, Birthday&gt; mBirthdayQuery =
- *       new IpcDataCache.QueryHandler&lt;Integer, Birthday&gt;() {
- *           {@literal @}Override
- *           public Birthday apply(Integer) {
- *              return GetService("birthdayd").getUserBirthday(userId);
- *           }
- *           {@literal @}Override
- *           public boolean shouldBypassQuery(Integer userId) {
- *               return userId == NEXT_BIRTHDAY;
- *           }
- *       };
- *   ...
- * }
- * </pre>
- *
- * If the {@code shouldBypassQuery()} method returns true then the cache is not used for that
- * particular query.  The {@code shouldBypassQuery()} method is not abstract and the default
- * implementation returns false.
- *
- * For security, there is a allowlist of processes that are allowed to invalidate a cache.
- * The allowlist includes normal runtime processes but does not include test processes.
- * Test processes must call {@code PropertyInvalidatedCache.disableForTestMode()} to disable
- * all cache activity in that process.
- *
- * Caching can be disabled completely by initializing {@code sEnabled} to false and rebuilding.
- *
- * To test a binder cache, create one or more tests that exercise the binder method.  This
- * should be done twice: once with production code and once with a special image that sets
- * {@code DEBUG} and {@code VERIFY} true.  In the latter case, verify that no cache
- * inconsistencies are reported.  If a cache inconsistency is reported, however, it might be a
- * false positive.  This happens if the server side data can be read and written non-atomically
- * with respect to cache invalidation.
+ * This interface is deprecated.  New clients should use {@link IpcDataCache} instead.  Internally,
+ * that class uses {@link PropertyInvalidatedCache} , but that design may change in the future.
  *
  * @param <Query> The class used to index cache entries: must be hashable and comparable
  * @param <Result> The class holding cache entries; use a boxed primitive if possible
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index daa15f0..9be928f 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -213,19 +213,17 @@
  * <a href="{@docRoot}guide/topics/admin/device-admin.html">Device Administration</a>
  * developer guide.
  *
- * <p id="devicepolicycontroller">Through <a href="#managed_provisioning">Managed Provisioning</a>,
- * Device Administrator apps can also be recognised as <b>
- Device Policy Controllers</b>. Device Policy Controllers can be one of
+ * <p id="devicepolicycontroller">Device Administrator apps can also be recognised as <b>
+ * Device Policy Controllers</b>. Device Policy Controllers can be one of
  * two types:
  * <ul>
  * <li>A <i id="deviceowner">Device Owner</i>, which only ever exists on the
- * {@link UserManager#isSystemUser System User} or {@link UserManager#isMainUser Main User}, is
+ * {@link UserManager#isSystemUser System User} or Main User, is
  * the most powerful type of Device Policy Controller and can affect policy across the device.
  * <li>A <i id="profileowner">Profile Owner<i>, which can exist on any user, can
  * affect policy on the user it is on, and when it is running on
  * {@link UserManager#isProfile a profile} has
- * <a href="#profile-on-parent">limited</a> ability to affect policy on its
- * {@link UserManager#getProfileParent parent}.
+ * <a href="#profile-on-parent">limited</a> ability to affect policy on its parent.
  * </ul>
  *
  * <p>Additional capabilities can be provided to Device Policy Controllers in
@@ -233,7 +231,7 @@
  * <ul>
  * <li>A Profile Owner on an <a href="#organization-owned">organization owned</a> device has access
  * to additional abilities, both <a href="#profile-on-parent-organization-owned">affecting policy on the profile's</a>
- * {@link UserManager#getProfileParent parent} and also the profile itself.
+ * parent and also the profile itself.
  * <li>A Profile Owner running on the {@link UserManager#isSystemUser System User} has access to
  * additional capabilities which affect the {@link UserManager#isSystemUser System User} and
  * also the whole device.
@@ -245,13 +243,12 @@
  * Controller</a>.
  *
  * <p><a href="#permissions">Permissions</a> are generally only given to apps
- * fulfilling particular key roles on the device (such as managing {@link DeviceLockManager
-device locks}).
+ * fulfilling particular key roles on the device (such as managing
+ * {@link android.devicelock.DeviceLockManager device locks}).
  *
  * <p id="roleholder"><b>Device Policy Management Role Holder</b>
- * <p>One app on the device fulfills the {@link RoleManager#ROLE_DEVICE_POLICY_MANAGEMENT Device
-Policy Management Role} and is trusted with managing the overall state of
- * Device Policy. This has access to much more powerful methods than
+ * <p>One app on the device fulfills the Device Policy Management Role and is trusted with managing
+ * the overall state of Device Policy. This has access to much more powerful methods than
  * <a href="#managingapps">managing apps</a>.
  *
  * <p id="querying"><b>Querying Device Policy</b>
@@ -273,7 +270,7 @@
  *
  * <p id="managed_profile">A <b>Managed Profile</b> enables data separation. For example to use
  * a device both for personal and corporate usage. The managed profile and its
- * {@link UserManager#getProfileParent parent} share a launcher.
+ * parent share a launcher.
  *
  * <p id="affiliated"><b>Affiliation</b>
  * <p>Using the {@link #setAffiliationIds} method, a
@@ -6643,7 +6640,7 @@
      * @param flags May be 0 or {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY}.
      * @throws SecurityException if the calling application does not own an active administrator
      *             that uses {@link DeviceAdminInfo#USES_POLICY_FORCE_LOCK} and the does not hold
-     *             the {@link android.Manifest.permission#LOCK_DEVICE} permission, or
+     *             the LOCK_DEVICE permission, or
      *             the {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY} flag is passed by an
      *             application that is not a profile owner of a managed profile.
      * @throws IllegalArgumentException if the {@link #FLAG_EVICT_CREDENTIAL_ENCRYPTION_KEY} flag is
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index abb562d..d8142fd 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -1042,10 +1042,11 @@
     }
 
     /**
-     * Get the available info about the AppWidget.
+     * Returns the {@link AppWidgetProviderInfo} for the specified AppWidget.
      *
-     * @return A appWidgetId.  If the appWidgetId has not been bound to a provider yet, or
-     * you don't have access to that appWidgetId, null is returned.
+     * @return Information regarding the provider of speficied widget, returns null if the
+     *         appWidgetId has not been bound to a provider yet, or you don't have access
+     *         to that widget.
      */
     public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) {
         if (mService == null) {
@@ -1390,7 +1391,7 @@
      *
      * @param provider The {@link ComponentName} for the {@link
      *    android.content.BroadcastReceiver BroadcastReceiver} provider for your AppWidget.
-     * @param extras In not null, this is passed to the launcher app. For eg {@link
+     * @param extras IF not null, this is passed to the launcher app. e.g. {@link
      *    #EXTRA_APPWIDGET_PREVIEW} can be used for a custom preview.
      * @param successCallback If not null, this intent will be sent when the widget is created.
      *
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 031380d..044178c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -20,6 +20,7 @@
 import static android.content.ContentProvider.maybeAddUserId;
 import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
 import static android.security.Flags.FLAG_FRP_ENFORCEMENT;
+import static android.security.Flags.preventIntentRedirect;
 
 import android.Manifest;
 import android.accessibilityservice.AccessibilityService;
@@ -7687,9 +7688,17 @@
     /** @hide */
     public static final int LOCAL_FLAG_FROM_SYSTEM = 1 << 5;
 
+    /**
+     * This flag indicates the creator token of this intent has been verified.
+     *
+     * @hide
+     */
+    public static final int LOCAL_FLAG_CREATOR_TOKEN_VERIFIED = 1 << 6;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "EXTENDED_FLAG_" }, value = {
             EXTENDED_FLAG_FILTER_MISMATCH,
+            EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ExtendedFlags {}
@@ -7703,6 +7712,13 @@
     @TestApi
     public static final int EXTENDED_FLAG_FILTER_MISMATCH = 1 << 0;
 
+    /**
+     * This flag indicates the creator token of this intent is either missing or invalid.
+     *
+     * @hide
+     */
+    public static final int EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN = 1 << 1;
+
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // toUri() and parseUri() options.
@@ -7870,6 +7886,7 @@
         this.mPackage = o.mPackage;
         this.mComponent = o.mComponent;
         this.mOriginalIntent = o.mOriginalIntent;
+        this.mCreatorTokenInfo = o.mCreatorTokenInfo;
 
         if (o.mCategories != null) {
             this.mCategories = new ArraySet<>(o.mCategories);
@@ -12176,6 +12193,60 @@
         return (mExtras != null) ? mExtras.describeContents() : 0;
     }
 
+    private static class CreatorTokenInfo {
+        // Stores a creator token for an intent embedded as an extra intent in a top level intent,
+        private IBinder mCreatorToken;
+        // Stores all extra keys whose values are intents for a top level intent.
+        private ArraySet<String> mExtraIntentKeys;
+    }
+
+    private @Nullable CreatorTokenInfo mCreatorTokenInfo;
+
+    /** @hide */
+    public void removeCreatorTokenInfo() {
+        mCreatorTokenInfo = null;
+    }
+
+    /** @hide */
+    public @Nullable IBinder getCreatorToken() {
+        return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mCreatorToken;
+    }
+
+    /** @hide */
+    public Set<String> getExtraIntentKeys() {
+        return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mExtraIntentKeys;
+    }
+
+    /** @hide */
+    public void setCreatorToken(@NonNull IBinder creatorToken) {
+        if (mCreatorTokenInfo == null) {
+            mCreatorTokenInfo = new CreatorTokenInfo();
+        }
+        mCreatorTokenInfo.mCreatorToken = creatorToken;
+    }
+
+    /**
+     * Collects keys in the extra bundle whose value are intents.
+     * @hide
+     */
+    public void collectExtraIntentKeys() {
+        if (!preventIntentRedirect()) return;
+
+        if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) {
+            for (String key : mExtras.keySet()) {
+                if (mExtras.get(key) instanceof Intent) {
+                    if (mCreatorTokenInfo == null) {
+                        mCreatorTokenInfo = new CreatorTokenInfo();
+                    }
+                    if (mCreatorTokenInfo.mExtraIntentKeys == null) {
+                        mCreatorTokenInfo.mExtraIntentKeys = new ArraySet<>();
+                    }
+                    mCreatorTokenInfo.mExtraIntentKeys.add(key);
+                }
+            }
+        }
+    }
+
     public void writeToParcel(Parcel out, int flags) {
         out.writeString8(mAction);
         Uri.writeToParcel(out, mData);
@@ -12225,6 +12296,16 @@
         } else {
             out.writeInt(0);
         }
+
+        if (preventIntentRedirect()) {
+            if (mCreatorTokenInfo == null) {
+                out.writeInt(0);
+            } else {
+                out.writeInt(1);
+                out.writeStrongBinder(mCreatorTokenInfo.mCreatorToken);
+                out.writeArraySet(mCreatorTokenInfo.mExtraIntentKeys);
+            }
+        }
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<Intent> CREATOR
@@ -12282,6 +12363,14 @@
         if (in.readInt() != 0) {
             mOriginalIntent = new Intent(in);
         }
+
+        if (preventIntentRedirect()) {
+            if (in.readInt() != 0) {
+                mCreatorTokenInfo = new CreatorTokenInfo();
+                mCreatorTokenInfo.mCreatorToken = in.readStrongBinder();
+                mCreatorTokenInfo.mExtraIntentKeys = (ArraySet<String>) in.readArraySet(null);
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 60b409a..160cbdf 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -301,4 +301,11 @@
     description: "Feature flag to remove hack code of using PackageManager.MATCH_ANY_USER flag without cross user permission."
     bug: "332664521"
     is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+    name: "delete_packages_silently_backport"
+    namespace: "package_manager_service"
+    description: "Feature flag to enable the holder of SYSTEM_APP_PROTECTION_SERVICE role to silently delete packages. To be deprecated by delete_packages_silently."
+    bug: "361776825"
+}
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index a2d24f6..73b5d94 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -432,6 +432,11 @@
     public abstract IntArray getDisplayGroupIds();
 
     /**
+     * Get all available display ids.
+     */
+    public abstract IntArray getDisplayIds();
+
+    /**
      * Called upon presentation started/ended on the display.
      * @param displayId the id of the display where presentation started.
      * @param isShown whether presentation is shown.
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 79cbd19..05e91e4 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1529,6 +1529,8 @@
          * config that can be used by
          * {@link SoundTriggerModule#startRecognition(int, RecognitionConfig)}
          *
+         * @deprecated should use builder-based constructor instead.
+         *             TODO(b/368042125): remove this method.
          * @param captureRequested Whether the DSP should capture the trigger sound.
          * @param allowMultipleTriggers Whether the service should restart listening after the DSP
          *                              triggers.
@@ -1539,6 +1541,8 @@
          *
          * @hide
          */
+        @Deprecated
+        @SuppressWarnings("Todo")
         @TestApi
         public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
                 @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
@@ -1695,6 +1699,89 @@
             result = prime * result + mAudioCapabilities;
             return result;
         }
+
+        /**
+         * Builder class for {@link RecognitionConfig} objects.
+         */
+        public static final class Builder {
+            private boolean mCaptureRequested;
+            private boolean mAllowMultipleTriggers;
+            @Nullable private KeyphraseRecognitionExtra[] mKeyphrases;
+            @Nullable private byte[] mData;
+            private int mAudioCapabilities;
+
+            /**
+             * Constructs a new Builder with the default values.
+             */
+            public Builder() {
+            }
+
+            /**
+             * Sets capture requested state.
+             * @param captureRequested The new requested state.
+             * @return the same Builder instance.
+             */
+            public @NonNull Builder setCaptureRequested(boolean captureRequested) {
+                mCaptureRequested = captureRequested;
+                return this;
+            }
+
+            /**
+             * Sets allow multiple triggers state.
+             * @param allowMultipleTriggers The new allow multiple triggers state.
+             * @return the same Builder instance.
+             */
+            public @NonNull Builder setAllowMultipleTriggers(boolean allowMultipleTriggers) {
+                mAllowMultipleTriggers = allowMultipleTriggers;
+                return this;
+            }
+
+            /**
+             * Sets the keyphrases field.
+             * @param keyphrases The new keyphrases.
+             * @return the same Builder instance.
+             */
+            public @NonNull Builder setKeyphrases(
+                    @NonNull Collection<KeyphraseRecognitionExtra> keyphrases) {
+                mKeyphrases = keyphrases.toArray(new KeyphraseRecognitionExtra[keyphrases.size()]);
+                return this;
+            }
+
+            /**
+             * Sets the data field.
+             * @param data The new data.
+             * @return the same Builder instance.
+             */
+            public @NonNull Builder setData(@Nullable byte[] data) {
+                mData = data;
+                return this;
+            }
+
+            /**
+             * Sets the audio capabilities field.
+             * @param audioCapabilities The new audio capabilities.
+             * @return the same Builder instance.
+             */
+            public @NonNull Builder setAudioCapabilities(int audioCapabilities) {
+                mAudioCapabilities = audioCapabilities;
+                return this;
+            }
+
+            /**
+             * Combines all of the parameters that have been set and return a new
+             * {@link RecognitionConfig} object.
+             * @return a new {@link RecognitionConfig} object
+             */
+            public @NonNull RecognitionConfig build() {
+                RecognitionConfig config = new RecognitionConfig(
+                        /* captureRequested= */ mCaptureRequested,
+                        /* allowMultipleTriggers= */ mAllowMultipleTriggers,
+                        /* keyphrases= */ mKeyphrases,
+                        /* data= */ mData,
+                        /* audioCapabilities= */ mAudioCapabilities);
+                return config;
+            }
+        };
     }
 
     /**
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 97e9f34..ed75491 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -1111,6 +1111,21 @@
     }
 
     /**
+     * Called whenever the stub implementation throws an exception which isn't propagated to the
+     * remote caller by the binder. If this method isn't overridden, this exception is swallowed,
+     * and some default return values are propagated to the caller.
+     *
+     * <br> <b> This should not throw. </b> Doing so would defeat the purpose of this handler, and
+     * suppress the exception it is handling.
+     *
+     * @param code The transaction code being handled
+     * @param e The exception which was thrown.
+     * @hide
+     */
+    protected void onUnhandledException(int code, int flags, Exception e) {
+    }
+
+    /**
      * @param in The raw file descriptor that an input data stream can be read from.
      * @param out The raw file descriptor that normal command messages should be written to.
      * @param err The raw file descriptor that command error messages should be written to.
@@ -1408,10 +1423,15 @@
                 } else {
                     Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
                 }
+                onUnhandledException(code, flags, e);
             } else {
                 // Clear the parcel before writing the exception.
                 reply.setDataSize(0);
                 reply.setDataPosition(0);
+                // The writeException below won't do anything useful if this is the case.
+                if (Parcel.getExceptionCode(e) == 0) {
+                    onUnhandledException(code, flags, e);
+                }
                 reply.writeException(e);
             }
             res = true;
diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java
index 0776cf4..e2a72dd 100644
--- a/core/java/android/os/IpcDataCache.java
+++ b/core/java/android/os/IpcDataCache.java
@@ -48,6 +48,20 @@
  * LRU cache that's invalidated when an opaque value in a property changes. Self-synchronizing,
  * but doesn't hold a lock across data fetches on query misses.
  *
+ * Clients should be aware of the following commonly-seen issues:
+ * <ul>
+ *
+ * <li>Client calls will not go through the cache before the first invalidation signal is
+ * received. Therefore, servers should signal an invalidation as soon as they have data to offer to
+ * clients.
+ *
+ * <li>Cache invalidation is restricted to well-known processes, which means that test code cannot
+ * invalidate a cache.  {@link #disableForTestMode()} and {@link #testPropertyName} must be used in
+ * test processes that attempt cache invalidation.  See
+ * {@link PropertyInvalidatedCacheTest#testBasicCache()} for an example.
+ *
+ * </ul>
+ *
  * The intended use case is caching frequently-read, seldom-changed information normally retrieved
  * across interprocess communication. Imagine that you've written a user birthday information
  * daemon called "birthdayd" that exposes an {@code IUserBirthdayService} interface over
@@ -136,20 +150,20 @@
  * string is permitted.  The third parameters is the name of the API being cached; this, too, can
  * any value.  The fourth is the name of the cache.  The cache is usually named after th API.
  * Some things you must know about the three strings:
- * <list>
- * <ul> The system property that controls the cache is named {@code cache_key.<module>.<api>}.
+ * <ul>
+ * <li> The system property that controls the cache is named {@code cache_key.<module>.<api>}.
  * Usually, the SELinux rules permit a process to write a system property (and therefore
  * invalidate a cache) based on the wildcard {@code cache_key.<module>.*}.  This means that
  * although the cache can be constructed with any module string, whatever string is chosen must be
  * consistent with the SELinux configuration.
- * <ul> The API name can be any string of alphanumeric characters.  All caches with the same API
+ * <li> The API name can be any string of alphanumeric characters.  All caches with the same API
  * are invalidated at the same time.  If a server supports several caches and all are invalidated
  * in common, then it is most efficient to assign the same API string to every cache.
- * <ul> The cache name can be any string.  In debug output, the name is used to distiguish between
+ * <li> The cache name can be any string.  In debug output, the name is used to distiguish between
  * caches with the same API name.  The cache name is also used when disabling caches in the
  * current process.  So, invalidation is based on the module+api but disabling (which is generally
  * a once-per-process operation) is based on the cache name.
- * </list>
+ * </ul>
  *
  * User birthdays do occasionally change, so we have to modify the server to invalidate this
  * cache when necessary. That invalidation code looks like this:
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index 56d3669..45e9def 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -52,3 +52,11 @@
     description: "Opt the system into enforcement of BAL"
     bug: "339403750"
 }
+
+flag {
+    name: "prevent_intent_redirect"
+    namespace: "responsible_apis"
+    description: "Prevent intent redirect attacks"
+    bug: "361143368"
+    is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/core/jni/android_util_XmlBlock.cpp b/core/jni/android_util_XmlBlock.cpp
index 5a444bb..c364451 100644
--- a/core/jni/android_util_XmlBlock.cpp
+++ b/core/jni/android_util_XmlBlock.cpp
@@ -83,7 +83,7 @@
         return 0;
     }
 
-    ResXMLParser* st = new ResXMLParser(*osb);
+    ResXMLParser* st = new(std::nothrow) ResXMLParser(*osb);
     if (st == NULL) {
         jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
         return 0;
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index d98836f..9821d43 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -25,6 +25,7 @@
         "BinderProxyCountingTestApp/src/**/*.java",
         "BinderProxyCountingTestService/src/**/*.java",
         "BinderDeathRecipientHelperApp/src/**/*.java",
+        "AppThatCallsBinderMethods/src/**/*.kt",
     ],
     visibility: ["//visibility:private"],
 }
@@ -104,6 +105,7 @@
         "mockito-target-extended-minus-junit4",
         "TestParameterInjector",
         "android.content.res.flags-aconfig-java",
+        "android.security.flags-aconfig-java",
     ],
 
     libs: [
@@ -143,6 +145,7 @@
         ":BinderProxyCountingTestApp",
         ":BinderProxyCountingTestService",
         ":AppThatUsesAppOps",
+        ":AppThatCallsBinderMethods",
     ],
 }
 
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index b1f1e2c..05ab783 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -26,6 +26,7 @@
         <option name="test-file-name" value="BinderProxyCountingTestApp.apk" />
         <option name="test-file-name" value="BinderProxyCountingTestService.apk" />
         <option name="test-file-name" value="AppThatUsesAppOps.apk" />
+        <option name="test-file-name" value="AppThatCallsBinderMethods.apk" />
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/core/tests/coretests/AppThatCallsBinderMethods/Android.bp b/core/tests/coretests/AppThatCallsBinderMethods/Android.bp
new file mode 100644
index 0000000..dcc0d4f
--- /dev/null
+++ b/core/tests/coretests/AppThatCallsBinderMethods/Android.bp
@@ -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.
+
+android_test_helper_app {
+    name: "AppThatCallsBinderMethods",
+    srcs: ["src/**/*.kt"],
+    platform_apis: true,
+    static_libs: ["coretests-aidl"],
+}
diff --git a/core/tests/coretests/AppThatCallsBinderMethods/AndroidManifest.xml b/core/tests/coretests/AppThatCallsBinderMethods/AndroidManifest.xml
new file mode 100644
index 0000000..b2f6d78
--- /dev/null
+++ b/core/tests/coretests/AppThatCallsBinderMethods/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.coretests.methodcallerhelperapp">
+    <application>
+        <receiver android:name="com.android.frameworks.coretests.methodcallerhelperapp.CallMethodsReceiver"
+                  android:exported="true"/>
+    </application>
+
+</manifest>
diff --git a/core/tests/coretests/AppThatCallsBinderMethods/src/com/android/frameworks/coretests/methodcallerhelperapp/CallMethodsReceiver.kt b/core/tests/coretests/AppThatCallsBinderMethods/src/com/android/frameworks/coretests/methodcallerhelperapp/CallMethodsReceiver.kt
new file mode 100644
index 0000000..638cc3b
--- /dev/null
+++ b/core/tests/coretests/AppThatCallsBinderMethods/src/com/android/frameworks/coretests/methodcallerhelperapp/CallMethodsReceiver.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.frameworks.coretests.methodcallerhelperapp
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+
+import com.android.frameworks.coretests.aidl.ITestInterface
+
+/**
+ * Receiver used to call methods when a binder is received
+ * {@link android.os.BinderUncaughtExceptionHandlerTest}.
+ */
+class CallMethodsReceiver : BroadcastReceiver() {
+    private val TAG = "CallMethodsReceiver"
+
+    override fun onReceive(context: Context, intent: Intent) {
+        try {
+            when (intent.getAction()) {
+                ACTION_CALL_METHOD -> intent.getExtras()!!.let {
+                    Log.i(TAG, "Received ACTION_CALL_METHOD with extras: $it")
+                    val iface = it.getBinder(EXTRA_BINDER)!!.let(ITestInterface.Stub::asInterface)!!
+                    val name = it.getString(EXTRA_METHOD_NAME)!!
+                    try {
+                        when (name) {
+                            "foo" -> iface.foo(5)
+                            "onewayFoo" -> iface.onewayFoo(5)
+                            "bar" -> iface.bar(5)
+                            else -> Log.e(TAG, "Unknown method name")
+                        }
+                    } catch (e: Exception) {
+                        // Exceptions expected
+                    }
+                }
+                else -> Log.e(TAG, "Unknown action " + intent.getAction())
+            }
+        } catch (e: Exception) {
+            Log.e(TAG, "Exception: ", e)
+        }
+    }
+}
diff --git a/core/tests/coretests/AppThatCallsBinderMethods/src/com/android/frameworks/coretests/methodcallerhelperapp/Constants.kt b/core/tests/coretests/AppThatCallsBinderMethods/src/com/android/frameworks/coretests/methodcallerhelperapp/Constants.kt
new file mode 100644
index 0000000..37c6268
--- /dev/null
+++ b/core/tests/coretests/AppThatCallsBinderMethods/src/com/android/frameworks/coretests/methodcallerhelperapp/Constants.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.frameworks.coretests.methodcallerhelperapp
+
+const val PACKAGE_NAME = "com.android.frameworks.coretests.methodcallerhelperapp"
+const val RECEIVER_NAME = "CallMethodsReceiver"
+const val ACTION_CALL_METHOD = PACKAGE_NAME + ".ACTION_CALL_METHOD"
+const val EXTRA_METHOD_NAME = PACKAGE_NAME + ".EXTRA_METHOD_NAME"
+const val EXTRA_BINDER = PACKAGE_NAME + ".EXTRA_BINDER"
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestInterface.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestInterface.aidl
new file mode 100644
index 0000000..ffcf178
--- /dev/null
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ITestInterface.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.aidl;
+
+/**
+ * Just an interface with a oneway, void and non-oneway method.
+ */
+interface ITestInterface {
+    // Method order matters, since we verify transaction codes
+    int foo(int a);
+    oneway void onewayFoo(int a);
+    void bar(int a);
+}
diff --git a/core/tests/coretests/src/android/content/IntentTest.java b/core/tests/coretests/src/android/content/IntentTest.java
new file mode 100644
index 0000000..d169ce3
--- /dev/null
+++ b/core/tests/coretests/src/android/content/IntentTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.content;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.security.Flags;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ *  Build/Install/Run:
+ *   atest FrameworksCoreTests:IntentTest
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class IntentTest {
+    private static final String TEST_ACTION = "android.content.IntentTest_test";
+    private static final String TEST_EXTRA_NAME = "testExtraName";
+    private static final Uri TEST_URI = Uri.parse("content://com.example/people");
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_PREVENT_INTENT_REDIRECT)
+    public void testReadFromParcelWithExtraIntentKeys() {
+        Intent intent = new Intent("TEST_ACTION");
+        intent.putExtra(TEST_EXTRA_NAME, new Intent(TEST_ACTION));
+        intent.putExtra(TEST_EXTRA_NAME + "2", 1);
+
+        intent.collectExtraIntentKeys();
+        final Parcel parcel = Parcel.obtain();
+        intent.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        final Intent target = new Intent();
+        target.readFromParcel(parcel);
+
+        assertEquals(intent.getAction(), target.getAction());
+        assertEquals(intent.getExtraIntentKeys(), target.getExtraIntentKeys());
+        assertThat(intent.getExtraIntentKeys()).hasSize(1);
+    }
+
+    @Test
+    public void testCreatorTokenInfo() {
+        Intent intent = new Intent(TEST_ACTION);
+        IBinder creatorToken = new Binder();
+
+        intent.setCreatorToken(creatorToken);
+        assertThat(intent.getCreatorToken()).isEqualTo(creatorToken);
+
+        intent.removeCreatorTokenInfo();
+        assertThat(intent.getCreatorToken()).isNull();
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_PREVENT_INTENT_REDIRECT)
+    public void testCollectExtraIntentKeys() {
+        Intent intent = new Intent(TEST_ACTION);
+        Intent extraIntent = new Intent(TEST_ACTION, TEST_URI);
+        intent.putExtra(TEST_EXTRA_NAME, extraIntent);
+
+        intent.collectExtraIntentKeys();
+
+        assertThat(intent.getExtraIntentKeys()).hasSize(1);
+        assertThat(intent.getExtraIntentKeys()).contains(TEST_EXTRA_NAME);
+    }
+
+}
diff --git a/core/tests/coretests/src/android/os/BinderUncaughtExceptionHandlerTest.kt b/core/tests/coretests/src/android/os/BinderUncaughtExceptionHandlerTest.kt
new file mode 100644
index 0000000..791c209
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BinderUncaughtExceptionHandlerTest.kt
@@ -0,0 +1,247 @@
+/*
+ * 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.content.Intent
+import android.platform.test.annotations.DisabledOnRavenwood
+import android.platform.test.annotations.Presubmit
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+
+import com.android.frameworks.coretests.aidl.ITestInterface
+import com.android.frameworks.coretests.methodcallerhelperapp.*
+
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.mockito.ArgumentMatcher
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.intThat
+import org.mockito.Mockito.after
+import org.mockito.Mockito.doThrow
+import org.mockito.Mockito.never
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.quality.Strictness.STRICT_STUBS
+
+private const val TIMEOUT_DURATION_MS = 2000L
+private const val FALSE_NEG_DURATION_MS = 500L
+private const val FLAG_ONEWAY = 1
+// From ITestInterface.Stub class, these values are package private
+private const val TRANSACTION_foo = 1
+private const val TRANSACTION_onewayFoo = 2
+private const val TRANSACTION_bar = 3
+
+/** Tests functionality of {@link android.os.Binder.onUnhandledException}. */
+@DisabledOnRavenwood(reason = "multi-app")
+@Presubmit
+@RunWith(AndroidJUnit4::class)
+class BinderUncaughtExceptionHandlerTest {
+
+    val mContext = InstrumentationRegistry.getInstrumentation().getTargetContext()
+
+    @Rule @JvmField val rule = MockitoJUnit.rule().strictness(STRICT_STUBS)
+
+    @Spy var mInterfaceImpl: ITestImpl = ITestImpl()
+
+    // This subclass is needed for visibility issues (via protected), since the method we are
+    // verifying lives on the boot classpath, it is not enough to be in the same package.
+    open class ITestImpl : ITestInterface.Stub() {
+        override fun onUnhandledException(code: Int, flags: Int, e: Exception?) =
+            onUnhandledExceptionVisible(code, flags, e)
+
+        public open fun onUnhandledExceptionVisible(code: Int, flags: Int, e: Exception?) {}
+
+        @Throws(RemoteException::class)
+        override open fun foo(x: Int): Int = throw UnsupportedOperationException()
+
+        @Throws(RemoteException::class)
+        override open fun onewayFoo(x: Int): Unit = throw UnsupportedOperationException()
+
+        @Throws(RemoteException::class)
+        override open fun bar(x: Int): Unit = throw UnsupportedOperationException()
+    }
+
+    class OnewayMatcher(private val isOneway: Boolean) : ArgumentMatcher<Int> {
+        override fun matches(argument: Int?) =
+            (argument!! and FLAG_ONEWAY) == if (isOneway) 1 else 0
+
+        override fun toString() = "Expected oneway: $isOneway"
+    }
+
+    @Test
+    fun testRegularMethod_ifThrowsRuntimeException_HandlerCalled() {
+        val myException = RuntimeException("Test exception")
+        doThrow(myException).`when`(mInterfaceImpl).foo(anyInt())
+
+        dispatchActionCall("foo")
+
+        verify(mInterfaceImpl, timeout(TIMEOUT_DURATION_MS))
+            .onUnhandledExceptionVisible(
+                /* transactionCode = */ eq(TRANSACTION_foo),
+                /* flags= */ intThat(OnewayMatcher(false)),
+                /* exception= */ eq(myException),
+            )
+        // No unexpected calls
+        verify(mInterfaceImpl).onUnhandledExceptionVisible(anyInt(), anyInt(), any())
+    }
+
+    @Test
+    fun testRegularMethod_ifThrowsRemoteException_HandlerCalled() {
+        val myException = RemoteException("Test exception")
+        doThrow(myException).`when`(mInterfaceImpl).foo(anyInt())
+
+        dispatchActionCall("foo")
+
+        verify(mInterfaceImpl, timeout(TIMEOUT_DURATION_MS))
+            .onUnhandledExceptionVisible(
+                /* transactionCode = */ eq(TRANSACTION_foo),
+                /* flags= */ intThat(OnewayMatcher(false)),
+                /* exception= */ eq(myException),
+            )
+        // No unexpected calls
+        verify(mInterfaceImpl).onUnhandledExceptionVisible(anyInt(), anyInt(), any())
+    }
+
+    @Test
+    fun testRegularMethod_ifThrowsSecurityException_HandlerNotCalled() {
+        val myException = SecurityException("Test exception")
+        doThrow(myException).`when`(mInterfaceImpl).foo(anyInt())
+
+        dispatchActionCall("foo")
+
+        // No unexpected calls
+        verify(mInterfaceImpl, after(FALSE_NEG_DURATION_MS).never())
+            .onUnhandledExceptionVisible(anyInt(), anyInt(), any())
+    }
+
+    @Test
+    fun testVoidMethod_ifThrowsRuntimeException_HandlerCalled() {
+        val myException = RuntimeException("Test exception")
+        doThrow(myException).`when`(mInterfaceImpl).bar(anyInt())
+
+        dispatchActionCall("bar")
+
+        verify(mInterfaceImpl, timeout(TIMEOUT_DURATION_MS))
+            .onUnhandledExceptionVisible(
+                /* transactionCode = */ eq(TRANSACTION_bar),
+                /* flags= */ intThat(OnewayMatcher(false)),
+                /* exception= */ eq(myException),
+            )
+        // No unexpected calls
+        verify(mInterfaceImpl).onUnhandledExceptionVisible(anyInt(), anyInt(), any())
+    }
+
+    @Test
+    fun testVoidMethod_ifThrowsRemoteException_HandlerCalled() {
+        val myException = RemoteException("Test exception")
+        doThrow(myException).`when`(mInterfaceImpl).bar(anyInt())
+
+        dispatchActionCall("bar")
+
+        verify(mInterfaceImpl, timeout(TIMEOUT_DURATION_MS))
+            .onUnhandledExceptionVisible(
+                /* transactionCode = */ eq(TRANSACTION_bar),
+                /* flags= */ intThat(OnewayMatcher(false)),
+                /* exception= */ eq(myException),
+            )
+        // No unexpected calls
+        verify(mInterfaceImpl).onUnhandledExceptionVisible(anyInt(), anyInt(), any())
+    }
+
+    @Test
+    fun testVoidMethod_ifThrowsSecurityException_HandlerNotCalled() {
+        val myException = SecurityException("Test exception")
+        doThrow(myException).`when`(mInterfaceImpl).bar(anyInt())
+
+        dispatchActionCall("bar")
+
+        // No unexpected calls
+        verify(mInterfaceImpl, after(FALSE_NEG_DURATION_MS).never())
+            .onUnhandledExceptionVisible(anyInt(), anyInt(), any())
+    }
+
+    @Test
+    fun testOnewayMethod_ifThrowsRuntimeException_HandlerCalled() {
+        val myException = RuntimeException("Test exception")
+        doThrow(myException).doNothing().`when`(mInterfaceImpl).onewayFoo(anyInt())
+
+        dispatchActionCall("onewayFoo")
+
+        verify(mInterfaceImpl, timeout(TIMEOUT_DURATION_MS))
+            .onUnhandledExceptionVisible(
+                /* transactionCode = */ eq(TRANSACTION_onewayFoo),
+                /* flags= */ intThat(OnewayMatcher(true)),
+                /* exception= */ eq(myException),
+            )
+        // No unexpected calls
+        verify(mInterfaceImpl).onUnhandledExceptionVisible(anyInt(), anyInt(), any())
+    }
+
+    @Test
+    fun testOnewayMethod_ifThrowsRemoteException_HandlerCalled() {
+        val myException = RemoteException("Test exception")
+        doThrow(myException).`when`(mInterfaceImpl).onewayFoo(anyInt())
+
+        dispatchActionCall("onewayFoo")
+
+        verify(mInterfaceImpl, timeout(TIMEOUT_DURATION_MS))
+            .onUnhandledExceptionVisible(
+                /* transactionCode = */ eq(TRANSACTION_onewayFoo),
+                /* flags= */ intThat(OnewayMatcher(true)),
+                /* exception= */ eq(myException),
+            )
+        // No unexpected calls
+        verify(mInterfaceImpl).onUnhandledExceptionVisible(anyInt(), anyInt(), any())
+    }
+
+    // All exceptions are uncaught for oneway
+    @Test
+    fun testOnewayMethod_ifThrowsSecurityException_HandlerCalled() {
+        val myException = SecurityException("Test exception")
+        doThrow(myException).`when`(mInterfaceImpl).onewayFoo(anyInt())
+
+        dispatchActionCall("onewayFoo")
+
+        verify(mInterfaceImpl, timeout(TIMEOUT_DURATION_MS))
+            .onUnhandledExceptionVisible(
+                /* transactionCode = */ eq(TRANSACTION_onewayFoo),
+                /* flags= */ intThat(OnewayMatcher(true)),
+                /* exception= */ eq(myException),
+            )
+        // No unexpected calls
+        verify(mInterfaceImpl).onUnhandledExceptionVisible(anyInt(), anyInt(), any())
+    }
+
+    private fun dispatchActionCall(methodName: String) =
+        Intent(ACTION_CALL_METHOD).apply {
+            putExtras(
+                Bundle().apply {
+                    putBinder(EXTRA_BINDER, mInterfaceImpl as IBinder)
+                    putString(EXTRA_METHOD_NAME, methodName)
+                }
+            )
+            setClassName(PACKAGE_NAME, CallMethodsReceiver::class.java.getName())
+        }.let { mContext.sendBroadcast(it) }
+}
diff --git a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
index 005538a..d9e90fa 100644
--- a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
+++ b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
@@ -29,6 +29,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -78,6 +79,16 @@
     }
 
     @Test
+    public void testNegatedDisabledFlag() {
+        assertThat(mResources.getBoolean(R.bool.bool5)).isTrue();
+    }
+
+    @Test
+    public void testNegatedEnabledFlag() {
+        assertThat(mResources.getBoolean(R.bool.bool6)).isTrue();
+    }
+
+    @Test
     public void testFlagEnabledDifferentCompilationUnit() {
         assertThat(mResources.getBoolean(R.bool.bool3)).isTrue();
     }
@@ -94,6 +105,26 @@
     }
 
     @Test
+    public void testDirectoryEnabledFlag() {
+        assertThat(mResources.getBoolean(R.bool.bool8)).isTrue();
+    }
+
+    @Test
+    public void testDirectoryDisabledFlag() {
+        assertThat(mResources.getBoolean(R.bool.bool7)).isTrue();
+    }
+
+    @Test
+    public void testDirectoryNegatedEnabledFlag() {
+        assertThat(mResources.getBoolean(R.bool.bool9)).isTrue();
+    }
+
+    @Test
+    public void testDirectoryNegatedDisabledFlag() {
+        assertThat(mResources.getBoolean(R.bool.bool10)).isTrue();
+    }
+
+    @Test
     public void testLayoutWithDisabledElements() {
         LinearLayout ll = (LinearLayout) getLayoutInflater().inflate(R.layout.layout1, null);
         assertThat(ll).isNotNull();
@@ -102,6 +133,24 @@
         assertThat((View) ll.findViewById(R.id.text2)).isNotNull();
     }
 
+    @Test
+    public void testEnabledFlagLayoutOverrides() {
+        LinearLayout ll = (LinearLayout) getLayoutInflater().inflate(R.layout.layout3, null);
+        assertThat(ll).isNotNull();
+        assertThat((View) ll.findViewById(R.id.text1)).isNotNull();
+        assertThat(((TextView) ll.findViewById(R.id.text1)).getText()).isEqualTo("foobar");
+    }
+
+    @Test(expected = Resources.NotFoundException.class)
+    public void testDisabledLayout() {
+        getLayoutInflater().inflate(R.layout.layout2, null);
+    }
+
+    @Test(expected = Resources.NotFoundException.class)
+    public void testDisabledDrawable() {
+        mResources.getDrawable(R.drawable.removedpng);
+    }
+
     private LayoutInflater getLayoutInflater() {
         ContextWrapper c = new ContextWrapper(mContext) {
             private LayoutInflater mInflater;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index b6476c9..ae46a99 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -50,6 +50,10 @@
 constexpr bool resample_gainmap_regions() {
     return false;
 }
+
+constexpr bool query_global_priority() {
+    return false;
+}
 }  // namespace hwui_flags
 #endif
 
@@ -110,6 +114,7 @@
 bool Properties::hdr10bitPlus = false;
 bool Properties::skipTelemetry = false;
 bool Properties::resampleGainmapRegions = false;
+bool Properties::queryGlobalPriority = false;
 
 int Properties::timeoutMultiplier = 1;
 
@@ -187,6 +192,7 @@
     hdr10bitPlus = hwui_flags::hdr_10bit_plus();
     resampleGainmapRegions = base::GetBoolProperty("debug.hwui.resample_gainmap_regions",
                                                    hwui_flags::resample_gainmap_regions());
+    queryGlobalPriority = hwui_flags::query_global_priority();
 
     timeoutMultiplier = android::base::GetIntProperty("ro.hw_timeout_multiplier", 1);
     skipTelemetry = base::GetBoolProperty(PROPERTY_SKIP_EGLMANAGER_TELEMETRY,
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index db47152..6f84796 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -346,6 +346,7 @@
     static bool hdr10bitPlus;
     static bool skipTelemetry;
     static bool resampleGainmapRegions;
+    static bool queryGlobalPriority;
 
     static int timeoutMultiplier;
 
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index ab052b9..93df478 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -129,3 +129,13 @@
   description: "APIs that expose gainmap metadata corresponding to those defined in ISO 21496-1"
   bug: "349357636"
 }
+
+flag {
+  name: "query_global_priority"
+  namespace: "core_graphics"
+  description: "Attempt to query whether the vulkan driver supports the requested global priority before queue creation."
+  bug: "343986434"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index e302393..6571d92 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -44,7 +44,7 @@
 namespace renderthread {
 
 // Not all of these are strictly required, but are all enabled if present.
-static std::array<std::string_view, 21> sEnableExtensions{
+static std::array<std::string_view, 23> sEnableExtensions{
         VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
         VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
         VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
@@ -65,6 +65,8 @@
         VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
         VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
         VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME,
+        VK_EXT_GLOBAL_PRIORITY_QUERY_EXTENSION_NAME,
+        VK_KHR_GLOBAL_PRIORITY_EXTENSION_NAME,
         VK_EXT_DEVICE_FAULT_EXTENSION_NAME,
 };
 
@@ -206,7 +208,7 @@
     GET_INST_PROC(GetPhysicalDeviceFeatures2);
     GET_INST_PROC(GetPhysicalDeviceImageFormatProperties2);
     GET_INST_PROC(GetPhysicalDeviceProperties);
-    GET_INST_PROC(GetPhysicalDeviceQueueFamilyProperties);
+    GET_INST_PROC(GetPhysicalDeviceQueueFamilyProperties2);
 
     uint32_t gpuCount;
     LOG_ALWAYS_FATAL_IF(mEnumeratePhysicalDevices(mInstance, &gpuCount, nullptr));
@@ -225,21 +227,30 @@
 
     // query to get the initial queue props size
     uint32_t queueCount = 0;
-    mGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueCount, nullptr);
+    mGetPhysicalDeviceQueueFamilyProperties2(mPhysicalDevice, &queueCount, nullptr);
     LOG_ALWAYS_FATAL_IF(!queueCount);
 
     // now get the actual queue props
-    std::unique_ptr<VkQueueFamilyProperties[]> queueProps(new VkQueueFamilyProperties[queueCount]);
-    mGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queueCount, queueProps.get());
+    std::unique_ptr<VkQueueFamilyProperties2[]>
+            queueProps(new VkQueueFamilyProperties2[queueCount]);
+    // query the global priority, this ignored if VK_EXT_global_priority isn't supported
+    std::vector<VkQueueFamilyGlobalPriorityPropertiesEXT> queuePriorityProps(queueCount);
+    for (uint32_t i = 0; i < queueCount; i++) {
+        queuePriorityProps[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_GLOBAL_PRIORITY_PROPERTIES_EXT;
+        queuePriorityProps[i].pNext = nullptr;
+        queueProps[i].pNext = &queuePriorityProps[i];
+    }
+    mGetPhysicalDeviceQueueFamilyProperties2(mPhysicalDevice, &queueCount, queueProps.get());
 
     constexpr auto kRequestedQueueCount = 2;
 
     // iterate to find the graphics queue
     mGraphicsQueueIndex = queueCount;
     for (uint32_t i = 0; i < queueCount; i++) {
-        if (queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+        if (queueProps[i].queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
             mGraphicsQueueIndex = i;
-            LOG_ALWAYS_FATAL_IF(queueProps[i].queueCount < kRequestedQueueCount);
+            LOG_ALWAYS_FATAL_IF(
+                    queueProps[i].queueFamilyProperties.queueCount < kRequestedQueueCount);
             break;
         }
     }
@@ -327,6 +338,15 @@
         tailPNext = &formatFeatures->pNext;
     }
 
+    VkPhysicalDeviceGlobalPriorityQueryFeaturesEXT* globalPriorityQueryFeatures =
+            new VkPhysicalDeviceGlobalPriorityQueryFeaturesEXT;
+    globalPriorityQueryFeatures->sType =
+            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_GLOBAL_PRIORITY_QUERY_FEATURES_EXT;
+    globalPriorityQueryFeatures->pNext = nullptr;
+    globalPriorityQueryFeatures->globalPriorityQuery = false;
+    *tailPNext = globalPriorityQueryFeatures;
+    tailPNext = &globalPriorityQueryFeatures->pNext;
+
     // query to get the physical device features
     mGetPhysicalDeviceFeatures2(mPhysicalDevice, &features);
     // this looks like it would slow things down,
@@ -341,24 +361,59 @@
 
     if (Properties::contextPriority != 0 &&
         grExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) {
-        memset(&queuePriorityCreateInfo, 0, sizeof(VkDeviceQueueGlobalPriorityCreateInfoEXT));
-        queuePriorityCreateInfo.sType =
-                VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT;
-        queuePriorityCreateInfo.pNext = nullptr;
+        VkQueueGlobalPriorityEXT globalPriority;
         switch (Properties::contextPriority) {
             case EGL_CONTEXT_PRIORITY_LOW_IMG:
-                queuePriorityCreateInfo.globalPriority = VK_QUEUE_GLOBAL_PRIORITY_LOW_EXT;
+                globalPriority = VK_QUEUE_GLOBAL_PRIORITY_LOW_EXT;
                 break;
             case EGL_CONTEXT_PRIORITY_MEDIUM_IMG:
-                queuePriorityCreateInfo.globalPriority = VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_EXT;
+                globalPriority = VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_EXT;
                 break;
             case EGL_CONTEXT_PRIORITY_HIGH_IMG:
-                queuePriorityCreateInfo.globalPriority = VK_QUEUE_GLOBAL_PRIORITY_HIGH_EXT;
+                globalPriority = VK_QUEUE_GLOBAL_PRIORITY_HIGH_EXT;
                 break;
             default:
                 LOG_ALWAYS_FATAL("Unsupported context priority");
         }
-        queueNextPtr = &queuePriorityCreateInfo;
+
+        // check if the requested priority is reported by the query
+        bool attachGlobalPriority = false;
+        if (uirenderer::Properties::queryGlobalPriority &&
+            globalPriorityQueryFeatures->globalPriorityQuery) {
+            for (uint32_t i = 0; i < queuePriorityProps[mGraphicsQueueIndex].priorityCount; i++) {
+                if (queuePriorityProps[mGraphicsQueueIndex].priorities[i] == globalPriority) {
+                    attachGlobalPriority = true;
+                    break;
+                }
+            }
+        } else {
+            // Querying is not supported so attempt queue creation with requested priority anyways
+            // If the priority turns out not to be supported, the driver *may* fail with
+            // VK_ERROR_NOT_PERMITTED_KHR
+            attachGlobalPriority = true;
+        }
+
+        if (attachGlobalPriority) {
+            memset(&queuePriorityCreateInfo, 0, sizeof(VkDeviceQueueGlobalPriorityCreateInfoEXT));
+            queuePriorityCreateInfo.sType =
+                    VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT;
+            queuePriorityCreateInfo.pNext = nullptr;
+            queuePriorityCreateInfo.globalPriority = globalPriority;
+            queueNextPtr = &queuePriorityCreateInfo;
+        } else {
+            // If globalPriorityQuery is enabled, attempting queue creation with an unsupported
+            // priority will return VK_ERROR_INITIALIZATION_FAILED.
+            //
+            // SysUI and Launcher will request HIGH when SF has RT but it is a known issue that
+            // upstream drm drivers currently lack a way to grant them the granular privileges
+            // they need for HIGH (but not RT) so they will fail queue creation.
+            // For now, drop the unsupported global priority request so that queue creation
+            // succeeds.
+            //
+            // Once that is fixed, this should probably be a fatal error indicating an improper
+            // request or an app needs to get the correct privileges.
+            ALOGW("Requested context priority is not supported by the queue");
+        }
     }
 
     const VkDeviceQueueCreateInfo queueInfo = {
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index f042571..a593ec6 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -152,7 +152,7 @@
     VkPtr<PFN_vkDestroyInstance> mDestroyInstance;
     VkPtr<PFN_vkEnumeratePhysicalDevices> mEnumeratePhysicalDevices;
     VkPtr<PFN_vkGetPhysicalDeviceProperties> mGetPhysicalDeviceProperties;
-    VkPtr<PFN_vkGetPhysicalDeviceQueueFamilyProperties> mGetPhysicalDeviceQueueFamilyProperties;
+    VkPtr<PFN_vkGetPhysicalDeviceQueueFamilyProperties2> mGetPhysicalDeviceQueueFamilyProperties2;
     VkPtr<PFN_vkGetPhysicalDeviceFeatures2> mGetPhysicalDeviceFeatures2;
     VkPtr<PFN_vkGetPhysicalDeviceImageFormatProperties2> mGetPhysicalDeviceImageFormatProperties2;
     VkPtr<PFN_vkCreateDevice> mCreateDevice;
diff --git a/packages/SettingsLib/ActionButtonsPreference/res/layout-v35/settingslib_expressive_action_buttons.xml b/packages/SettingsLib/ActionButtonsPreference/res/layout-v35/settingslib_expressive_action_buttons.xml
index fc63c0f..3e73ebd 100644
--- a/packages/SettingsLib/ActionButtonsPreference/res/layout-v35/settingslib_expressive_action_buttons.xml
+++ b/packages/SettingsLib/ActionButtonsPreference/res/layout-v35/settingslib_expressive_action_buttons.xml
@@ -35,11 +35,13 @@
             style="@style/SettingsLibActionButton.Expressive"
             android:layout_width="@dimen/settingslib_expressive_space_large3"
             android:layout_height="@dimen/settingslib_expressive_space_medium5"
-            android:layout_gravity="center_horizontal" />
+            android:layout_gravity="center_horizontal"
+            android:importantForAccessibility="no"/>
         <TextView
             android:id="@+id/text1"
             style="@style/SettingsLibActionButton.Expressive.Label"
-            android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"/>
+            android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"
+            android:importantForAccessibility="no"/>
 
     </LinearLayout>
 
@@ -55,11 +57,13 @@
             style="@style/SettingsLibActionButton.Expressive"
             android:layout_width="@dimen/settingslib_expressive_space_large3"
             android:layout_height="@dimen/settingslib_expressive_space_medium5"
-            android:layout_gravity="center_horizontal" />
+            android:layout_gravity="center_horizontal"
+            android:importantForAccessibility="no"/>
         <TextView
             android:id="@+id/text2"
             style="@style/SettingsLibActionButton.Expressive.Label"
-            android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"/>
+            android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"
+            android:importantForAccessibility="no"/>
 
     </LinearLayout>
 
@@ -75,11 +79,13 @@
             style="@style/SettingsLibActionButton.Expressive"
             android:layout_width="@dimen/settingslib_expressive_space_large3"
             android:layout_height="@dimen/settingslib_expressive_space_medium5"
-            android:layout_gravity="center_horizontal" />
+            android:layout_gravity="center_horizontal"
+            android:importantForAccessibility="no"/>
         <TextView
             android:id="@+id/text3"
             style="@style/SettingsLibActionButton.Expressive.Label"
-            android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"/>
+            android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"
+            android:importantForAccessibility="no"/>
 
     </LinearLayout>
 
@@ -95,10 +101,12 @@
             style="@style/SettingsLibActionButton.Expressive"
             android:layout_width="@dimen/settingslib_expressive_space_large3"
             android:layout_height="@dimen/settingslib_expressive_space_medium5"
-            android:layout_gravity="center_horizontal" />
+            android:layout_gravity="center_horizontal"
+            android:importantForAccessibility="no"/>
         <TextView
             android:id="@+id/text4"
             style="@style/SettingsLibActionButton.Expressive.Label"
-            android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"/>
+            android:layout_marginTop="@dimen/settingslib_expressive_space_extrasmall3"
+            android:importantForAccessibility="no"/>
     </LinearLayout>
 </LinearLayout>
diff --git a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
index f011039..b286182 100644
--- a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
+++ b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java
@@ -548,16 +548,18 @@
                 if (mButton instanceof MaterialButton) {
                     ((MaterialButton) mButton).setIcon(mIcon);
                 }
+                mButton.setEnabled(mIsEnabled);
+                mActionLayout.setOnClickListener(mListener);
+                mActionLayout.setEnabled(mIsEnabled);
+                mActionLayout.setContentDescription(mText);
             } else {
                 mButton.setText(mText);
                 mButton.setCompoundDrawablesWithIntrinsicBounds(
                         null /* left */, mIcon /* top */, null /* right */, null /* bottom */);
+                mButton.setOnClickListener(mListener);
+                mButton.setEnabled(mIsEnabled);
             }
 
-            mButton.setOnClickListener(mListener);
-            mButton.setEnabled(mIsEnabled);
-
-
             if (shouldBeVisible()) {
                 mButton.setVisibility(View.VISIBLE);
                 if (mIsExpressive) {
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 0cb85d8..d4851e1 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -32,6 +32,7 @@
         "SettingsLibBannerMessagePreference",
         "SettingsLibBarChartPreference",
         "SettingsLibButtonPreference",
+        "SettingsLibBulletPreference",
         "SettingsLibCollapsingToolbarBaseActivity",
         "SettingsLibDeviceStateRotationLock",
         "SettingsLibDisplayUtils",
@@ -53,6 +54,7 @@
         "SettingsLibTwoTargetPreference",
         "SettingsLibUsageProgressBarPreference",
         "SettingsLibUtils",
+        "SettingsLibZeroStatePreference",
         "settingslib_media_flags_lib",
         "settingslib_flags_lib",
     ],
diff --git a/packages/SettingsLib/BulletPreference/Android.bp b/packages/SettingsLib/BulletPreference/Android.bp
new file mode 100644
index 0000000..3ea0b2b
--- /dev/null
+++ b/packages/SettingsLib/BulletPreference/Android.bp
@@ -0,0 +1,33 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+    name: "SettingsLibBulletPreference",
+    use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
+
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    resource_dirs: ["res"],
+
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.preference_preference",
+        "SettingsLibSettingsTheme",
+    ],
+    sdk_version: "system_current",
+    min_sdk_version: "21",
+    apex_available: [
+        "//apex_available:platform",
+    ],
+}
diff --git a/packages/SettingsLib/BulletPreference/AndroidManifest.xml b/packages/SettingsLib/BulletPreference/AndroidManifest.xml
new file mode 100644
index 0000000..c7495ef
--- /dev/null
+++ b/packages/SettingsLib/BulletPreference/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.settingslib.widget.preference.bullet">
+
+    <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/BulletPreference/res/layout/settingslib_expressive_bullet_icon_frame.xml b/packages/SettingsLib/BulletPreference/res/layout/settingslib_expressive_bullet_icon_frame.xml
new file mode 100644
index 0000000..030f024
--- /dev/null
+++ b/packages/SettingsLib/BulletPreference/res/layout/settingslib_expressive_bullet_icon_frame.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2024 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/icon_frame"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:minWidth="@dimen/settingslib_expressive_space_medium4"
+    android:gravity="top|start"
+    android:layout_marginTop="@dimen/settingslib_expressive_space_small1"
+    android:paddingStart="@dimen/settingslib_expressive_space_extrasmall4"
+    android:paddingEnd="@dimen/settingslib_expressive_space_small1">
+
+    <androidx.preference.internal.PreferenceImageView
+        android:id="@android:id/icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:maxWidth="@dimen/settingslib_expressive_space_small4"
+        app:maxHeight="@dimen/settingslib_expressive_space_small4"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/BulletPreference/res/layout/settingslib_expressive_bullet_preference.xml b/packages/SettingsLib/BulletPreference/res/layout/settingslib_expressive_bullet_preference.xml
new file mode 100644
index 0000000..3f37f6c
--- /dev/null
+++ b/packages/SettingsLib/BulletPreference/res/layout/settingslib_expressive_bullet_preference.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/selectableItemBackground"
+    android:clipToPadding="false"
+    android:baselineAligned="false">
+
+    <include layout="@layout/settingslib_expressive_bullet_icon_frame"/>
+
+    <include layout="@layout/settingslib_expressive_preference_text_frame"/>
+
+    <!-- Preference should place its actual preference widget here. -->
+    <LinearLayout
+        android:id="@android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="end|center_vertical"
+        android:paddingStart="@dimen/settingslib_expressive_space_small1"
+        android:paddingEnd="0dp"
+        android:orientation="vertical"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/BulletPreference/src/com/android/settingslib/widget/BulletPreference.kt b/packages/SettingsLib/BulletPreference/src/com/android/settingslib/widget/BulletPreference.kt
new file mode 100644
index 0000000..45e2e9a
--- /dev/null
+++ b/packages/SettingsLib/BulletPreference/src/com/android/settingslib/widget/BulletPreference.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.settingslib.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+
+import com.android.settingslib.widget.preference.bullet.R
+
+/**
+ * The BulletPreference shows a text which describe a feature.
+ */
+class BulletPreference @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0
+) : Preference(context, attrs, defStyleAttr, defStyleRes) {
+
+    init {
+        layoutResource = R.layout.settingslib_expressive_bullet_preference
+    }
+
+    override fun onBindViewHolder(holder: PreferenceViewHolder) {
+        super.onBindViewHolder(holder)
+        holder.isDividerAllowedAbove = false
+        holder.isDividerAllowedBelow = false
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_bullet_start.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_bullet_start.xml
new file mode 100644
index 0000000..9216c96
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_icon_bullet_start.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2024 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?android:attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M354,673L480,597L606,674L573,530L684,434L538,421L480,285L422,420L276,433L387,530L354,673ZM233,840L298,559L80,370L368,345L480,80L592,345L880,370L662,559L727,840L480,691L233,840ZM480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490Z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference.xml
index dda7517c..952562e 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference.xml
@@ -31,37 +31,7 @@
 
     <include layout="@layout/settingslib_icon_frame"/>
 
-    <RelativeLayout
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:paddingTop="16dp"
-        android:paddingBottom="16dp">
-
-        <TextView
-            android:id="@android:id/title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="start"
-            android:textAlignment="viewStart"
-            android:maxLines="2"
-            android:textAppearance="?android:attr/textAppearanceListItem"
-            android:ellipsize="marquee"/>
-
-        <TextView
-            android:id="@android:id/summary"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_below="@android:id/title"
-            android:layout_alignLeft="@android:id/title"
-            android:layout_alignStart="@android:id/title"
-            android:layout_gravity="start"
-            android:textAlignment="viewStart"
-            android:textColor="?android:attr/textColorSecondary"
-            android:maxLines="10"
-            style="@style/PreferenceSummaryTextStyle"/>
-
-    </RelativeLayout>
+    <include layout="@layout/settingslib_preference_frame"/>
 
     <!-- Preference should place its actual preference widget here. -->
     <LinearLayout
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference_frame.xml
new file mode 100644
index 0000000..433d264
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v31/settingslib_preference_frame.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2024 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="0dp"
+    android:layout_height="wrap_content"
+    android:layout_weight="1"
+    android:paddingTop="16dp"
+    android:paddingBottom="16dp">
+
+    <TextView
+        android:id="@android:id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start"
+        android:textAlignment="viewStart"
+        android:text="Title"
+        android:maxLines="2"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:ellipsize="marquee"/>
+
+    <TextView
+        android:id="@android:id/summary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@android:id/title"
+        android:layout_alignLeft="@android:id/title"
+        android:layout_alignStart="@android:id/title"
+        android:text="Summary summary summary"
+        android:layout_gravity="start"
+        android:textAlignment="viewStart"
+        android:textColor="?android:attr/textColorSecondary"
+        android:maxLines="10"
+        style="@style/PreferenceSummaryTextStyle"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference.xml b/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference.xml
index fedcc77..4e23b65 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference.xml
@@ -31,41 +31,7 @@
 
     <include layout="@layout/settingslib_icon_frame"/>
 
-    <RelativeLayout
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:paddingTop="16dp"
-        android:paddingBottom="16dp">
-
-        <TextView
-            android:id="@android:id/title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="start"
-            android:textAlignment="viewStart"
-            android:maxLines="2"
-            android:hyphenationFrequency="normalFast"
-            android:lineBreakWordStyle="phrase"
-            android:textAppearance="?android:attr/textAppearanceListItem"
-            android:ellipsize="marquee"/>
-
-        <TextView
-            android:id="@android:id/summary"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_below="@android:id/title"
-            android:layout_alignLeft="@android:id/title"
-            android:layout_alignStart="@android:id/title"
-            android:layout_gravity="start"
-            android:textAlignment="viewStart"
-            android:textColor="?android:attr/textColorSecondary"
-            android:maxLines="10"
-            android:hyphenationFrequency="normalFast"
-            android:lineBreakWordStyle="phrase"
-            style="@style/PreferenceSummaryTextStyle"/>
-
-    </RelativeLayout>
+    <include layout="@layout/settingslib_preference_frame"/>
 
     <!-- Preference should place its actual preference widget here. -->
     <LinearLayout
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference_frame.xml
new file mode 100644
index 0000000..f93e1b9
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v33/settingslib_preference_frame.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2024 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="0dp"
+    android:layout_height="wrap_content"
+    android:layout_weight="1"
+    android:paddingTop="16dp"
+    android:paddingBottom="16dp">
+
+    <TextView
+        android:id="@android:id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start"
+        android:textAlignment="viewStart"
+        android:text="Title"
+        android:maxLines="2"
+        android:hyphenationFrequency="normalFast"
+        android:lineBreakWordStyle="phrase"
+        android:textAppearance="?android:attr/textAppearanceListItem"
+        android:ellipsize="marquee"/>
+
+    <TextView
+        android:id="@android:id/summary"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_below="@android:id/title"
+        android:layout_alignLeft="@android:id/title"
+        android:layout_alignStart="@android:id/title"
+        android:layout_gravity="start"
+        android:textAlignment="viewStart"
+        android:textColor="?android:attr/textColorSecondary"
+        android:maxLines="10"
+        android:text="Summary summary summary"
+        android:hyphenationFrequency="normalFast"
+        android:lineBreakWordStyle="phrase"
+        style="@style/PreferenceSummaryTextStyle"/>
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/ZeroStatePreference/Android.bp b/packages/SettingsLib/ZeroStatePreference/Android.bp
new file mode 100644
index 0000000..4fc00bd
--- /dev/null
+++ b/packages/SettingsLib/ZeroStatePreference/Android.bp
@@ -0,0 +1,33 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+    name: "SettingsLibZeroStatePreference",
+    use_resource_processor: true,
+    defaults: [
+        "SettingsLintDefaults",
+    ],
+
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    resource_dirs: ["res"],
+
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.preference_preference",
+        "SettingsLibSettingsTheme",
+    ],
+    sdk_version: "system_current",
+    min_sdk_version: "28",
+    apex_available: [
+        "//apex_available:platform",
+    ],
+}
diff --git a/packages/SettingsLib/ZeroStatePreference/AndroidManifest.xml b/packages/SettingsLib/ZeroStatePreference/AndroidManifest.xml
new file mode 100644
index 0000000..51b0ab8
--- /dev/null
+++ b/packages/SettingsLib/ZeroStatePreference/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.settingslib.widget.preference.zerostate">
+
+    <uses-sdk android:minSdkVersion="28" />
+
+</manifest>
diff --git a/packages/SettingsLib/ZeroStatePreference/res/drawable/settingslib_expressive_zerostate_background.xml b/packages/SettingsLib/ZeroStatePreference/res/drawable/settingslib_expressive_zerostate_background.xml
new file mode 100644
index 0000000..f42b441
--- /dev/null
+++ b/packages/SettingsLib/ZeroStatePreference/res/drawable/settingslib_expressive_zerostate_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2024 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="160dp"
+    android:height="160dp"
+    android:viewportWidth="161"
+    android:viewportHeight="161">
+    <path
+        android:pathData="M67.2,4.43C74.79,-1.41 85.35,-1.41 92.94,4.43L112.01,19.1C113.48,20.23 115.09,21.16 116.8,21.87L139.02,31.08C147.86,34.74 153.14,43.9 151.89,53.4L148.74,77.28C148.5,79.12 148.5,80.98 148.74,82.82L151.89,106.71C153.14,116.2 147.86,125.36 139.02,129.03L116.8,138.23C115.09,138.95 113.48,139.87 112.01,141L92.94,155.67C85.35,161.51 74.79,161.51 67.2,155.67L48.13,141C46.66,139.87 45.05,138.95 43.34,138.23L21.12,129.03C12.28,125.36 7,116.2 8.25,106.71L11.4,82.82C11.64,80.98 11.64,79.12 11.4,77.28L8.25,53.4C7,43.9 12.28,34.74 21.12,31.08L43.34,21.87C45.05,21.16 46.66,20.23 48.13,19.1L67.2,4.43Z"
+        android:fillColor="@color/settingslib_materialColorSurfaceContainerHigh"/>
+</vector>
diff --git a/packages/SettingsLib/ZeroStatePreference/res/layout/settingslib_expressive_preference_zerostate.xml b/packages/SettingsLib/ZeroStatePreference/res/layout/settingslib_expressive_preference_zerostate.xml
new file mode 100644
index 0000000..c0b195c
--- /dev/null
+++ b/packages/SettingsLib/ZeroStatePreference/res/layout/settingslib_expressive_preference_zerostate.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center"
+    android:gravity="center"
+    android:orientation="vertical">
+
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        tools:ignore="ContentDescription">
+
+        <ImageView
+            android:layout_width="@dimen/settingslib_expressive_zero_state_background_size"
+            android:layout_height="@dimen/settingslib_expressive_zero_state_background_size"
+            android:src="@drawable/settingslib_expressive_zerostate_background"/>
+
+        <ImageView
+            android:id="@android:id/icon"
+            android:layout_width="@dimen/settingslib_expressive_space_large3"
+            android:layout_height="@dimen/settingslib_expressive_space_large3"
+            android:layout_gravity="center"/>
+
+    </FrameLayout>
+    <TextView
+        android:id="@android:id/title"
+        android:layout_width="@dimen/settingslib_expressive_zero_state_title_width"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        android:layout_marginTop="@dimen/settingslib_expressive_space_small4"/>
+    <TextView
+        android:id="@android:id/summary"
+        android:layout_width="@dimen/settingslib_expressive_zero_state_title_width"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:layout_marginTop="@dimen/settingslib_expressive_space_small4"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        android:textColor="?android:attr/textColorSecondary"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/ZeroStatePreference/res/values/dimens.xml b/packages/SettingsLib/ZeroStatePreference/res/values/dimens.xml
new file mode 100644
index 0000000..e981ecc
--- /dev/null
+++ b/packages/SettingsLib/ZeroStatePreference/res/values/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <dimen name="settingslib_expressive_zero_state_background_size">160dp</dimen>
+    <dimen name="settingslib_expressive_zero_state_title_width">316dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/ZeroStatePreference/src/com/android/settingslib/widget/ZeroStatePreference.kt b/packages/SettingsLib/ZeroStatePreference/src/com/android/settingslib/widget/ZeroStatePreference.kt
new file mode 100644
index 0000000..9b1ccef
--- /dev/null
+++ b/packages/SettingsLib/ZeroStatePreference/src/com/android/settingslib/widget/ZeroStatePreference.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget
+
+import android.content.Context
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.widget.ImageView
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+
+import com.android.settingslib.widget.preference.zerostate.R
+
+class ZeroStatePreference @JvmOverloads constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0
+) : Preference(context, attrs, defStyleAttr, defStyleRes) {
+
+    private val iconTint: Int = context.getColor(
+        com.android.settingslib.widget.theme.R.color.settingslib_materialColorOnSecondaryContainer
+    )
+    private var tintedIcon: Drawable? = null
+
+    init {
+        isSelectable = false
+        layoutResource = R.layout.settingslib_expressive_preference_zerostate
+        icon?.let { originalIcon ->
+            tintedIcon = originalIcon.mutate().apply {
+                colorFilter = PorterDuffColorFilter(iconTint, PorterDuff.Mode.SRC_IN)
+            }
+        }
+    }
+
+    override fun onBindViewHolder(holder: PreferenceViewHolder) {
+        super.onBindViewHolder(holder)
+
+        holder.itemView.isFocusable = false
+        holder.itemView.isClickable = false
+
+        (holder.findViewById(android.R.id.icon) as? ImageView)?.apply {
+            setImageDrawable(tintedIcon ?: icon)
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
index bfeaf92..0f6e6a7 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import com.android.systemui.scene.ui.composable.Scene
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationLockscreenScrimViewModel
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
@@ -35,12 +36,7 @@
 import javax.inject.Provider
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-@Module(
-    includes =
-        [
-            LockscreenSceneBlueprintModule::class,
-        ],
-)
+@Module(includes = [LockscreenSceneBlueprintModule::class])
 interface LockscreenSceneModule {
 
     @Binds @IntoSet fun lockscreenScene(scene: LockscreenScene): Scene
@@ -51,9 +47,7 @@
         @Provides
         @SysUISingleton
         @KeyguardRootView
-        fun viewProvider(
-            configurator: Provider<KeyguardViewConfigurator>,
-        ): () -> View {
+        fun viewProvider(configurator: Provider<KeyguardViewConfigurator>): () -> View {
             return { configurator.get().getKeyguardRootView() }
         }
 
@@ -67,10 +61,16 @@
         @Provides
         fun providesLockscreenContent(
             viewModelFactory: LockscreenContentViewModel.Factory,
+            notificationScrimViewModelFactory: NotificationLockscreenScrimViewModel.Factory,
             blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
             clockInteractor: KeyguardClockInteractor,
         ): LockscreenContent {
-            return LockscreenContent(viewModelFactory, blueprints, clockInteractor)
+            return LockscreenContent(
+                viewModelFactory,
+                notificationScrimViewModelFactory,
+                blueprints,
+                clockInteractor,
+            )
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index dbe7538..5c5514a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -30,6 +30,8 @@
 import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
 import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.notifications.ui.composable.NotificationLockscreenScrim
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationLockscreenScrimViewModel
 
 /**
  * Renders the content of the lockscreen.
@@ -39,6 +41,7 @@
  */
 class LockscreenContent(
     private val viewModelFactory: LockscreenContentViewModel.Factory,
+    private val notificationScrimViewModelFactory: NotificationLockscreenScrimViewModel.Factory,
     private val blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
     private val clockInteractor: KeyguardClockInteractor,
 ) {
@@ -47,10 +50,13 @@
     }
 
     @Composable
-    fun SceneScope.Content(
-        modifier: Modifier = Modifier,
-    ) {
-        val viewModel = rememberViewModel("LockscreenContent") { viewModelFactory.create() }
+    fun SceneScope.Content(modifier: Modifier = Modifier) {
+        val viewModel =
+            rememberViewModel("LockscreenContent-viewModel") { viewModelFactory.create() }
+        val notificationLockscreenScrimViewModel =
+            rememberViewModel("LockscreenContent-scrimViewModel") {
+                notificationScrimViewModelFactory.create()
+            }
         val isContentVisible: Boolean by viewModel.isContentVisible.collectAsStateWithLifecycle()
         if (!isContentVisible) {
             // If the content isn't supposed to be visible, show a large empty box as it's needed
@@ -71,6 +77,9 @@
         }
 
         val blueprint = blueprintByBlueprintId[blueprintId] ?: return
-        with(blueprint) { Content(viewModel, modifier.sysuiResTag("keyguard_root_view")) }
+        with(blueprint) {
+            Content(viewModel, modifier.sysuiResTag("keyguard_root_view"))
+            NotificationLockscreenScrim(notificationLockscreenScrimViewModel)
+        }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationLockscreenScrim.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationLockscreenScrim.kt
new file mode 100644
index 0000000..4279be3
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationLockscreenScrim.kt
@@ -0,0 +1,119 @@
+/*
+ * 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.notifications.ui.composable
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.content.state.TransitionState
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationLockscreenScrimViewModel
+import kotlinx.coroutines.launch
+
+/**
+ * A full-screen notifications scrim that is only visible after transitioning from Shade scene to
+ * Lockscreen Scene and ending user input, at which point it fades out, visually completing the
+ * transition.
+ */
+@Composable
+fun SceneScope.NotificationLockscreenScrim(
+    viewModel: NotificationLockscreenScrimViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val coroutineScope = rememberCoroutineScope()
+    val shadeMode = viewModel.shadeMode.collectAsStateWithLifecycle()
+
+    // Important: Make sure that shouldShowScrimFadeOut() is checked the first time the Lockscreen
+    // scene is composed.
+    val useFadeOutOnComposition =
+        remember(shadeMode.value) {
+            layoutState.currentTransition?.let { currentTransition ->
+                shouldShowScrimFadeOut(currentTransition, shadeMode.value)
+            } ?: false
+        }
+
+    val alphaAnimatable = remember { Animatable(1f) }
+
+    LaunchedEffect(
+        alphaAnimatable,
+        layoutState.currentTransition,
+        useFadeOutOnComposition,
+        shadeMode,
+    ) {
+        val currentTransition = layoutState.currentTransition
+        if (
+            useFadeOutOnComposition &&
+                currentTransition != null &&
+                shouldShowScrimFadeOut(currentTransition, shadeMode.value) &&
+                currentTransition.isUserInputOngoing
+        ) {
+            // keep scrim visible until user lifts their finger.
+            viewModel.setAlphaForLockscreenFadeIn(0f)
+            alphaAnimatable.snapTo(1f)
+        } else if (
+            useFadeOutOnComposition &&
+                (currentTransition == null ||
+                    (shouldShowScrimFadeOut(currentTransition, shadeMode.value) &&
+                        !currentTransition.isUserInputOngoing))
+        ) {
+            // we no longer want to keep the scrim from fading out, so animate the scrim fade-out
+            // and pipe the progress to the view model as well, so NSSL can fade-in the stack in
+            // tandem.
+            viewModel.setAlphaForLockscreenFadeIn(0f)
+            coroutineScope.launch {
+                snapshotFlow { alphaAnimatable.value }
+                    .collect { viewModel.setAlphaForLockscreenFadeIn(1 - it) }
+            }
+            alphaAnimatable.animateTo(0f, tween())
+        } else {
+            // disable the scrim fade logic.
+            viewModel.setAlphaForLockscreenFadeIn(1f)
+            alphaAnimatable.snapTo(0f)
+        }
+    }
+
+    Box(
+        modifier
+            .fillMaxSize()
+            .element(Notifications.Elements.NotificationScrim)
+            .graphicsLayer { alpha = alphaAnimatable.value }
+            .background(MaterialTheme.colorScheme.surface)
+    )
+}
+
+private fun shouldShowScrimFadeOut(
+    currentTransition: TransitionState.Transition,
+    shadeMode: ShadeMode,
+): Boolean {
+    return shadeMode == ShadeMode.Single &&
+        currentTransition.isInitiatedByUserInput &&
+        (currentTransition.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen) ||
+            currentTransition.isTransitioning(from = Scenes.Bouncer, to = Scenes.Lockscreen))
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index fe4a65b..2066c93 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -86,6 +86,8 @@
 import com.android.compose.animation.scene.LowestZIndexContentPicker
 import com.android.compose.animation.scene.NestedScrollBehavior
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.animation.scene.SceneTransitionLayoutState
+import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.modifiers.thenIf
 import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
 import com.android.systemui.res.R
@@ -101,7 +103,7 @@
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import kotlin.math.roundToInt
-import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 
 object Notifications {
@@ -251,6 +253,7 @@
         NotificationPlaceholder(
             stackScrollView = stackScrollView,
             viewModel = viewModel,
+            useStackBounds = { shouldUseLockscreenStackBounds(layoutState.transitionState) },
             modifier = Modifier.fillMaxSize(),
         )
         HeadsUpNotificationSpace(
@@ -363,7 +366,6 @@
         snapshotFlow { syntheticScroll.value }
             .collect { delta ->
                 scrollNotificationStack(
-                    scope = coroutineScope,
                     delta = delta,
                     animate = false,
                     scrimOffset = scrimOffset,
@@ -383,7 +385,6 @@
                 // composed at least once), and our remote input row overlaps with the ime bounds.
                 if (isRemoteInputActive && imeTopValue > 0f && remoteInputRowBottom > imeTopValue) {
                     scrollNotificationStack(
-                        scope = coroutineScope,
                         delta = remoteInputRowBottom - imeTopValue,
                         animate = true,
                         scrimOffset = scrimOffset,
@@ -450,7 +451,10 @@
                                 scrimCornerRadius,
                                 screenCornerRadius,
                                 { expansionFraction },
-                                shouldPunchHoleBehindScrim,
+                                shouldAnimateScrimCornerRadius(
+                                    layoutState,
+                                    shouldPunchHoleBehindScrim,
+                                ),
                             )
                             .let { scrimRounding.value.toRoundedCornerShape(it) }
                     clip = true
@@ -514,6 +518,9 @@
                 NotificationPlaceholder(
                     stackScrollView = stackScrollView,
                     viewModel = viewModel,
+                    useStackBounds = {
+                        !shouldUseLockscreenStackBounds(layoutState.transitionState)
+                    },
                     modifier =
                         Modifier.notificationStackHeight(
                                 view = stackScrollView,
@@ -600,6 +607,7 @@
 private fun SceneScope.NotificationPlaceholder(
     stackScrollView: NotificationScrollView,
     viewModel: NotificationsPlaceholderViewModel,
+    useStackBounds: () -> Boolean,
     modifier: Modifier = Modifier,
 ) {
     Box(
@@ -609,21 +617,26 @@
                 .debugBackground(viewModel, DEBUG_STACK_COLOR)
                 .onSizeChanged { size -> debugLog(viewModel) { "STACK onSizeChanged: size=$size" } }
                 .onGloballyPositioned { coordinates: LayoutCoordinates ->
-                    val positionInWindow = coordinates.positionInWindow()
-                    debugLog(viewModel) {
-                        "STACK onGloballyPositioned:" +
-                            " size=${coordinates.size}" +
-                            " position=$positionInWindow" +
-                            " bounds=${coordinates.boundsInWindow()}"
+                    // This element is opted out of the shared element system, so there can be
+                    // multiple instances of it during a transition. Thus we need to determine which
+                    // instance should feed its bounds to NSSL to avoid providing conflicting values
+                    val useBounds = useStackBounds()
+                    if (useBounds) {
+                        // NOTE: positionInWindow.y scrolls off screen, but boundsInWindow.top won't
+                        val positionInWindow = coordinates.positionInWindow()
+                        debugLog(viewModel) {
+                            "STACK onGloballyPositioned:" +
+                                " size=${coordinates.size}" +
+                                " position=$positionInWindow" +
+                                " bounds=${coordinates.boundsInWindow()}"
+                        }
+                        stackScrollView.setStackTop(positionInWindow.y)
                     }
-                    // NOTE: positionInWindow.y scrolls off screen, but boundsInWindow.top will not
-                    stackScrollView.setStackTop(positionInWindow.y)
                 }
     )
 }
 
 private suspend fun scrollNotificationStack(
-    scope: CoroutineScope,
     delta: Float,
     animate: Boolean,
     scrimOffset: Animatable<Float, AnimationVector1D>,
@@ -638,7 +651,7 @@
             if (animate) {
                 // launch a new coroutine for the remainder animation so that it doesn't suspend the
                 // scrim animation, allowing both to play simultaneously.
-                scope.launch { scrollState.animateScrollTo(remainingDelta) }
+                coroutineScope { launch { scrollState.animateScrollTo(remainingDelta) } }
             } else {
                 scrollState.scrollTo(remainingDelta)
             }
@@ -658,6 +671,18 @@
     }
 }
 
+private fun shouldUseLockscreenStackBounds(state: TransitionState): Boolean {
+    return state is TransitionState.Idle && state.currentScene == Scenes.Lockscreen
+}
+
+private fun shouldAnimateScrimCornerRadius(
+    state: SceneTransitionLayoutState,
+    shouldPunchHoleBehindScrim: Boolean,
+): Boolean {
+    return shouldPunchHoleBehindScrim ||
+        state.isTransitioning(from = Scenes.Shade, to = Scenes.Lockscreen)
+}
+
 private fun calculateCornerRadius(
     scrimCornerRadius: Dp,
     screenCornerRadius: Dp,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index 58fbf43..303a6f0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -82,18 +82,35 @@
         sharedElement(Notifications.Elements.HeadsUpNotificationPlaceholder, enabled = false)
     }
     from(Scenes.Shade, to = Scenes.QuickSettings) { shadeToQuickSettingsTransition() }
+    from(Scenes.Shade, to = Scenes.Lockscreen) {
+        reversed { lockscreenToShadeTransition() }
+        sharedElement(Notifications.Elements.NotificationStackPlaceholder, enabled = false)
+    }
 
     // Overlay transitions
 
     to(Overlays.NotificationsShade) { toNotificationsShadeTransition() }
     to(Overlays.QuickSettingsShade) { toQuickSettingsShadeTransition() }
-    from(Overlays.NotificationsShade, Overlays.QuickSettingsShade) {
+    from(Overlays.NotificationsShade, to = Overlays.QuickSettingsShade) {
         notificationsShadeToQuickSettingsShadeTransition()
     }
+    from(Scenes.Gone, to = Overlays.NotificationsShade, key = SlightlyFasterShadeCollapse) {
+        toNotificationsShadeTransition(durationScale = 0.9)
+    }
+    from(Scenes.Gone, to = Overlays.QuickSettingsShade, key = SlightlyFasterShadeCollapse) {
+        toQuickSettingsShadeTransition(durationScale = 0.9)
+    }
+    from(Scenes.Lockscreen, to = Overlays.NotificationsShade, key = SlightlyFasterShadeCollapse) {
+        toNotificationsShadeTransition(durationScale = 0.9)
+    }
+    from(Scenes.Lockscreen, to = Overlays.QuickSettingsShade, key = SlightlyFasterShadeCollapse) {
+        toQuickSettingsShadeTransition(durationScale = 0.9)
+    }
 
     // Scene overscroll
 
     overscrollDisabled(Scenes.Gone, Orientation.Vertical)
+    overscrollDisabled(Scenes.Lockscreen, Orientation.Vertical)
     overscroll(Scenes.Bouncer, Orientation.Vertical) {
         translate(Bouncer.Elements.Content, y = { absoluteDistance })
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
index ac54896..4c0efd2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
@@ -4,13 +4,19 @@
 import androidx.compose.animation.core.tween
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.TransitionBuilder
+import com.android.compose.animation.scene.UserActionDistance
 import com.android.systemui.bouncer.ui.composable.Bouncer
 
 const val FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION = 0.5f
+const val FROM_LOCK_SCREEN_TO_BOUNCER_SWIPE_DISTANCE_FRACTION = 0.5f
 
 fun TransitionBuilder.lockscreenToBouncerTransition() {
     spec = tween(durationMillis = 500)
 
+    distance = UserActionDistance { fromSceneSize, _ ->
+        fromSceneSize.height * FROM_LOCK_SCREEN_TO_BOUNCER_SWIPE_DISTANCE_FRACTION
+    }
+
     translate(Bouncer.Elements.Content, y = 300.dp)
     fractionRange(end = FROM_LOCK_SCREEN_TO_BOUNCER_FADE_FRACTION) {
         fade(Bouncer.Elements.Background)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 7f2ee2a..db0fe3e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -296,7 +296,7 @@
 
     val shouldPunchHoleBehindScrim =
         layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
-            layoutState.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade)
+            layoutState.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Shade)
     // Media is visible and we are in landscape on a small height screen
     val mediaInRow = isMediaVisible && isLandscape()
     val mediaOffset by
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index a08fbbf..fa304c9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -198,6 +198,13 @@
     @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
     fun testTransitionToGlanceableHubOnWake() =
         testScope.runTest {
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.DREAMING,
+                testScope,
+            )
+            reset(transitionRepository)
+
             whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
             kosmos.setCommunalAvailable(true)
             runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
index 88a1df1..3388c75 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.notifications.ui.viewmodel
 
+import android.platform.test.annotations.EnableFlags
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -25,6 +26,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.shade.ui.viewmodel.notificationsShadeOverlayContentViewModel
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
@@ -36,13 +38,14 @@
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 @EnableSceneContainer
+@EnableFlags(DualShade.FLAG_NAME)
 class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val sceneInteractor = kosmos.sceneInteractor
 
-    private val underTest = kosmos.notificationsShadeOverlayContentViewModel
+    private val underTest by lazy { kosmos.notificationsShadeOverlayContentViewModel }
 
     @Test
     fun onScrimClicked_hidesShade() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
index abd1e2c..8c7ec47 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.ui.viewmodel
 
+import android.platform.test.annotations.EnableFlags
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -25,6 +26,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runTest
@@ -35,13 +37,14 @@
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 @EnableSceneContainer
+@EnableFlags(DualShade.FLAG_NAME)
 class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val sceneInteractor = kosmos.sceneInteractor
 
-    private val underTest = kosmos.quickSettingsShadeOverlayContentViewModel
+    private val underTest by lazy { kosmos.quickSettingsShadeOverlayContentViewModel }
 
     @Test
     fun onScrimClicked_hidesShade() =
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 a0cafcb..c9e958d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -341,7 +341,7 @@
             bouncerActionButton?.onClick?.invoke()
             runCurrent()
 
-            // TODO(b/298026988): Assert that an activity was started once we use ActivityStarter.
+            // TODO(b/369765704): Assert that an activity was started once we use ActivityStarter.
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
index d163abf..19ac0cf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImplTest.kt
@@ -287,7 +287,7 @@
 
     @Test
     fun anyExpansion_shadeGreater() =
-        testScope.runTest() {
+        testScope.runTest {
             // WHEN shade is more expanded than QS
             shadeTestUtil.setShadeAndQsExpansion(.5f, 0f)
             runCurrent()
@@ -298,7 +298,7 @@
 
     @Test
     fun anyExpansion_qsGreater() =
-        testScope.runTest() {
+        testScope.runTest {
             // WHEN qs is more expanded than shade
             shadeTestUtil.setShadeAndQsExpansion(0f, .5f)
             runCurrent()
@@ -308,6 +308,36 @@
         }
 
     @Test
+    fun isShadeAnyExpanded_shadeCollapsed() =
+        testScope.runTest {
+            val isShadeAnyExpanded by collectLastValue(underTest.isShadeAnyExpanded)
+            shadeTestUtil.setShadeExpansion(0f)
+            runCurrent()
+
+            assertThat(isShadeAnyExpanded).isFalse()
+        }
+
+    @Test
+    fun isShadeAnyExpanded_shadePartiallyExpanded() =
+        testScope.runTest {
+            val isShadeAnyExpanded by collectLastValue(underTest.isShadeAnyExpanded)
+            shadeTestUtil.setShadeExpansion(0.01f)
+            runCurrent()
+
+            assertThat(isShadeAnyExpanded).isTrue()
+        }
+
+    @Test
+    fun isShadeAnyExpanded_shadeFullyExpanded() =
+        testScope.runTest {
+            val isShadeAnyExpanded by collectLastValue(underTest.isShadeAnyExpanded)
+            shadeTestUtil.setShadeExpansion(1f)
+            runCurrent()
+
+            assertThat(isShadeAnyExpanded).isTrue()
+        }
+
+    @Test
     fun isShadeTouchable_isFalse_whenDeviceAsleepAndNotPulsing() =
         testScope.runTest {
             powerRepository.updateWakefulness(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
index 109cd05..4592b60 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImplTest.kt
@@ -35,6 +35,7 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertThrows
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -382,4 +383,44 @@
             // THEN user is not interacting
             assertThat(actual).isFalse()
         }
+
+    @Test
+    fun expandNotificationsShade_unsupported() =
+        testScope.runTest {
+            assertThrows(UnsupportedOperationException::class.java) {
+                underTest.expandNotificationsShade("reason")
+            }
+        }
+
+    @Test
+    fun expandQuickSettingsShade_unsupported() =
+        testScope.runTest {
+            assertThrows(UnsupportedOperationException::class.java) {
+                underTest.expandQuickSettingsShade("reason")
+            }
+        }
+
+    @Test
+    fun collapseNotificationsShade_unsupported() =
+        testScope.runTest {
+            assertThrows(UnsupportedOperationException::class.java) {
+                underTest.collapseNotificationsShade("reason")
+            }
+        }
+
+    @Test
+    fun collapseQuickSettingsShade_unsupported() =
+        testScope.runTest {
+            assertThrows(UnsupportedOperationException::class.java) {
+                underTest.collapseQuickSettingsShade("reason")
+            }
+        }
+
+    @Test
+    fun collapseEitherShade_unsupported() =
+        testScope.runTest {
+            assertThrows(UnsupportedOperationException::class.java) {
+                underTest.collapseEitherShade("reason")
+            }
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index f6fe667ff..eb8ea8b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -1,12 +1,15 @@
 package com.android.systemui.shade.ui.viewmodel
 
 import android.content.Intent
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.provider.AlarmClock
 import android.provider.Settings
 import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -18,7 +21,9 @@
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor
 import com.android.systemui.testKosmos
@@ -48,7 +53,7 @@
     private val sceneInteractor = kosmos.sceneInteractor
     private val deviceEntryInteractor = kosmos.deviceEntryInteractor
 
-    private val underTest: ShadeHeaderViewModel = kosmos.shadeHeaderViewModel
+    private val underTest by lazy { kosmos.shadeHeaderViewModel }
 
     @Before
     fun setUp() {
@@ -96,6 +101,7 @@
         }
 
     @Test
+    @DisableFlags(DualShade.FLAG_NAME)
     fun onSystemIconContainerClicked_locked_collapsesShadeToLockscreen() =
         testScope.runTest {
             setDeviceEntered(false)
@@ -108,6 +114,25 @@
         }
 
     @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun onSystemIconContainerClicked_lockedOnDualShade_collapsesShadeToLockscreen() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            setDeviceEntered(false)
+            setScene(Scenes.Lockscreen)
+            setOverlay(Overlays.NotificationsShade)
+            assertThat(currentOverlays).isNotEmpty()
+
+            underTest.onSystemIconContainerClicked()
+            runCurrent()
+
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).isEmpty()
+        }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
     fun onSystemIconContainerClicked_unlocked_collapsesShadeToGone() =
         testScope.runTest {
             setDeviceEntered(true)
@@ -119,6 +144,24 @@
             assertThat(sceneInteractor.currentScene.value).isEqualTo(Scenes.Gone)
         }
 
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun onSystemIconContainerClicked_unlockedOnDualShade_collapsesShadeToGone() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            setDeviceEntered(true)
+            setScene(Scenes.Gone)
+            setOverlay(Overlays.NotificationsShade)
+            assertThat(currentOverlays).isNotEmpty()
+
+            underTest.onSystemIconContainerClicked()
+            runCurrent()
+
+            assertThat(currentScene).isEqualTo(Scenes.Gone)
+            assertThat(currentOverlays).isEmpty()
+        }
+
     companion object {
         private val SUB_1 =
             SubscriptionModel(
@@ -144,6 +187,17 @@
         testScope.runCurrent()
     }
 
+    private fun setOverlay(key: OverlayKey) {
+        val currentOverlays = sceneInteractor.currentOverlays.value + key
+        sceneInteractor.showOverlay(key, "test")
+        sceneInteractor.setTransitionState(
+            MutableStateFlow<ObservableTransitionState>(
+                ObservableTransitionState.Idle(sceneInteractor.currentScene.value, currentOverlays)
+            )
+        )
+        testScope.runCurrent()
+    }
+
     private fun TestScope.setDeviceEntered(isEntered: Boolean) {
         if (isEntered) {
             // Unlock the device marking the device has entered.
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt
index 8b5a09b..2c026c0 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt
@@ -103,7 +103,7 @@
                 prepareToPerformAction()
                 returnToCall()
             },
-            onLongClick = null
+            onLongClick = null,
         )
     }
 
@@ -115,15 +115,15 @@
                 dozeLogger.logEmergencyCall()
                 startEmergencyDialerActivity()
             },
-            // TODO(b/308001302): The long click detector doesn't work properly, investigate.
+            // TODO(b/369767936): The long click detector doesn't work properly, investigate.
             onLongClick = {
                 if (emergencyAffordanceManager.needsEmergencyAffordance()) {
                     prepareToPerformAction()
 
-                    // TODO(b/298026988): Check that !longPressWasDragged before invoking.
+                    // TODO(b/369767936): Check that !longPressWasDragged before invoking.
                     emergencyAffordanceManager.performEmergencyCall()
                 }
-            }
+            },
         )
     }
 
@@ -143,7 +143,7 @@
             applicationContext.startActivityAsUser(
                 this,
                 ActivityOptions.makeCustomAnimation(applicationContext, 0, 0).toBundle(),
-                UserHandle(selectedUserInteractor.getSelectedUserId())
+                UserHandle(selectedUserInteractor.getSelectedUserId()),
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 493afde..aa1873c 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -2298,9 +2298,11 @@
                     }
 
                     @Override
-                    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+                    public boolean onScroll(@Nullable MotionEvent e1, MotionEvent e2,
+                            float distanceX,
                             float distanceY) {
                         if (distanceY < 0 && distanceY > distanceX
+                                && e1 != null
                                 && e1.getY() <= mStatusBarWindowController.getStatusBarHeight()) {
                             // Downwards scroll from top
                             openShadeAndDismiss();
@@ -2310,9 +2312,11 @@
                     }
 
                     @Override
-                    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+                    public boolean onFling(@Nullable MotionEvent e1, MotionEvent e2,
+                            float velocityX,
                             float velocityY) {
                         if (velocityY > 0 && Math.abs(velocityY) > Math.abs(velocityX)
+                                && e1 != null
                                 && e1.getY() <= mStatusBarWindowController.getStatusBarHeight()) {
                             // Downwards fling from top
                             openShadeAndDismiss();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 68a252b..654c763 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -515,7 +515,13 @@
     }
 
     protected void notifyChange() {
-        mBgHandler.post(() -> mContentResolver.notifyChange(mSliceUri, null /* observer */));
+        mBgHandler.post(() -> {
+            try {
+                mContentResolver.notifyChange(mSliceUri, null /* observer */);
+            } catch (Exception e) {
+                Log.e(TAG, "Error on mContentResolver.notifyChange()", e);
+            }
+        });
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 416eaba..063adc8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -63,6 +63,7 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.KeyguardIndicationController
 import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationLockscreenScrimViewModel
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -102,6 +103,7 @@
     private val keyguardClockViewModel: KeyguardClockViewModel,
     private val smartspaceViewModel: KeyguardSmartspaceViewModel,
     private val lockscreenContentViewModelFactory: LockscreenContentViewModel.Factory,
+    private val notificationScrimViewModelFactory: NotificationLockscreenScrimViewModel.Factory,
     private val lockscreenSceneBlueprintsLazy: Lazy<Set<LockscreenSceneBlueprint>>,
     private val clockInteractor: KeyguardClockInteractor,
     private val keyguardViewMediator: KeyguardViewMediator,
@@ -207,6 +209,7 @@
     private fun createLockscreen(
         context: Context,
         viewModelFactory: LockscreenContentViewModel.Factory,
+        notificationScrimViewModelFactory: NotificationLockscreenScrimViewModel.Factory,
         blueprints: Set<@JvmSuppressWildcards LockscreenSceneBlueprint>,
     ): View {
         val sceneBlueprints =
@@ -222,6 +225,8 @@
                         with(
                             LockscreenContent(
                                 viewModelFactory = viewModelFactory,
+                                notificationScrimViewModelFactory =
+                                    notificationScrimViewModelFactory,
                                 blueprints = sceneBlueprints,
                                 clockInteractor = clockInteractor,
                             )
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
index 5be225c..219e45c 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
@@ -16,8 +16,7 @@
 
 package com.android.systemui.notifications.ui.viewmodel
 
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import dagger.assisted.AssistedFactory
@@ -34,13 +33,10 @@
 constructor(
     val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
     val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
-    private val sceneInteractor: SceneInteractor,
+    private val shadeInteractor: ShadeInteractor,
 ) {
     fun onScrimClicked() {
-        sceneInteractor.hideOverlay(
-            overlay = Overlays.NotificationsShade,
-            loggingReason = "Shade scrim clicked",
-        )
+        shadeInteractor.collapseNotificationsShade(loggingReason = "Shade scrim clicked")
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
index 3b97d82..7c8fbea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
@@ -16,8 +16,7 @@
 
 package com.android.systemui.qs.ui.viewmodel
 
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -31,15 +30,12 @@
 class QuickSettingsShadeOverlayContentViewModel
 @AssistedInject
 constructor(
-    val sceneInteractor: SceneInteractor,
+    val shadeInteractor: ShadeInteractor,
     val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
     val quickSettingsContainerViewModel: QuickSettingsContainerViewModel,
 ) {
     fun onScrimClicked() {
-        sceneInteractor.hideOverlay(
-            overlay = Overlays.QuickSettingsShade,
-            loggingReason = "Shade scrim clicked",
-        )
+        shadeInteractor.collapseQuickSettingsShade(loggingReason = "Shade scrim clicked")
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index ac49e91..559c263 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -248,7 +248,8 @@
                         } else if (action == ACTION_UP) {
                             // Gesture was too short to be picked up by scene container touch
                             // handling; programmatically start the transition to the shade.
-                            mShadeInteractor.get().expandNotificationShade("short launcher swipe");
+                            mShadeInteractor.get()
+                                    .expandNotificationsShade("short launcher swipe", null);
                         }
                     }
                     event.recycle();
@@ -265,7 +266,8 @@
                         mSceneInteractor.get().onRemoteUserInputStarted(
                                 "trackpad swipe");
                     } else if (action == ACTION_UP) {
-                        mShadeInteractor.get().expandNotificationShade("short trackpad swipe");
+                        mShadeInteractor.get()
+                                .expandNotificationsShade("short trackpad swipe", null);
                     }
                     mStatusBarWinController.getWindowRootView().dispatchTouchEvent(event);
                 } else {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
index b9f57f2..3c6d858 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKeys.kt
@@ -32,4 +32,7 @@
      * normal collapse would.
      */
     val SlightlyFasterShadeCollapse = TransitionKey("SlightlyFasterShadeCollapse")
+
+    /** Reference to a content transition that should happen instantly, i.e. without animation. */
+    val Instant = TransitionKey("Instant")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 361226a4..6c99282 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -22,8 +22,8 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.Instant
 import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse
 import com.android.systemui.shade.ShadeController.ShadeVisibilityListener
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -80,18 +80,25 @@
         }
     }
 
+    @Deprecated("Deprecated in Java")
     override fun isShadeEnabled() = shadeInteractor.isShadeEnabled.value
 
+    @Deprecated("Deprecated in Java")
     override fun isShadeFullyOpen(): Boolean = shadeInteractor.isAnyFullyExpanded.value
 
+    @Deprecated("Deprecated in Java")
     override fun isExpandingOrCollapsing(): Boolean = shadeInteractor.isUserInteracting.value
 
+    @Deprecated("Deprecated in Java")
     override fun instantExpandShade() {
         // Do nothing
     }
 
     override fun instantCollapseShade() {
-        sceneInteractor.snapToScene(SceneFamilies.Home, "hide shade")
+        shadeInteractor.collapseNotificationsShade(
+            loggingReason = "ShadeControllerSceneImpl.instantCollapseShade",
+            transitionKey = Instant,
+        )
     }
 
     override fun animateCollapseShade(
@@ -122,16 +129,17 @@
         }
     }
 
+    @Deprecated("Deprecated in Java")
     override fun collapseWithDuration(animationDuration: Int) {
         // TODO(b/300258424) inline this. The only caller uses the default duration.
         animateCollapseShade()
     }
 
     private fun animateCollapseShadeInternal() {
-        sceneInteractor.changeScene(
-            SceneFamilies.Home, // TODO(b/336581871): add sceneState?
-            "ShadeController.animateCollapseShade",
-            SlightlyFasterShadeCollapse,
+        // TODO(b/336581871): add sceneState?
+        shadeInteractor.collapseEitherShade(
+            loggingReason = "ShadeController.animateCollapseShade",
+            transitionKey = SlightlyFasterShadeCollapse,
         )
     }
 
@@ -140,6 +148,7 @@
         animateCollapseShade()
     }
 
+    @Deprecated("Deprecated in Java")
     override fun closeShadeIfOpen(): Boolean {
         if (shadeInteractor.isAnyExpanded.value) {
             commandQueue.animateCollapsePanels(
@@ -155,6 +164,7 @@
         animateCollapseShadeForcedDelayed()
     }
 
+    @Deprecated("Deprecated in Java")
     override fun collapseShade(animate: Boolean) {
         if (animate) {
             animateCollapseShade()
@@ -163,13 +173,14 @@
         }
     }
 
+    @Deprecated("Deprecated in Java")
     override fun collapseOnMainThread() {
         // TODO if this works with delegation alone, we can deprecate and delete
         collapseShade()
     }
 
     override fun expandToNotifications() {
-        shadeInteractor.expandNotificationShade("ShadeController.animateExpandShade")
+        shadeInteractor.expandNotificationsShade("ShadeController.animateExpandShade")
     }
 
     override fun expandToQs() {
@@ -193,14 +204,17 @@
         }
     }
 
+    @Deprecated("Deprecated in Java")
     override fun postAnimateCollapseShade() {
         animateCollapseShade()
     }
 
+    @Deprecated("Deprecated in Java")
     override fun postAnimateForceCollapseShade() {
         animateCollapseShadeForced()
     }
 
+    @Deprecated("Deprecated in Java")
     override fun postAnimateExpandQs() {
         expandToQs()
     }
@@ -214,18 +228,23 @@
         }
     }
 
+    @Deprecated("Deprecated in Java")
     override fun makeExpandedInvisible() {
         // Do nothing
     }
 
+    @Deprecated("Deprecated in Java")
     override fun makeExpandedVisible(force: Boolean) {
         // Do nothing
     }
 
+    @Deprecated("Deprecated in Java")
     override fun isExpandedVisible(): Boolean {
-        return sceneInteractor.currentScene.value != Scenes.Gone
+        return sceneInteractor.currentScene.value != Scenes.Gone ||
+            sceneInteractor.currentOverlays.value.isNotEmpty()
     }
 
+    @Deprecated("Deprecated in Java")
     override fun onStatusBarTouch(event: MotionEvent) {
         // The only call to this doesn't happen with MigrateClocksToBlueprint.isEnabled enabled
         throw UnsupportedOperationException()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index b046c50..a3f2c64 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.shade.domain.interactor
 
 import androidx.annotation.FloatRange
+import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.shade.shared.model.ShadeMode
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -27,19 +28,22 @@
 
 /** Business logic for shade interactions. */
 interface ShadeInteractor : BaseShadeInteractor {
-    /** Emits true if the shade is currently allowed and false otherwise. */
+    /** Emits true if the Notifications shade is currently allowed and false otherwise. */
     val isShadeEnabled: StateFlow<Boolean>
 
-    /** Emits true if QS is currently allowed and false otherwise. */
+    /** Emits true if QS shade is currently allowed and false otherwise. */
     val isQsEnabled: StateFlow<Boolean>
 
-    /** Whether either the shade or QS is fully expanded. */
+    /** Whether either the Notifications shade or QS shade is fully expanded. */
     val isAnyFullyExpanded: StateFlow<Boolean>
 
-    /** Whether the Shade is fully expanded. */
+    /** Whether the Notifications Shade is fully expanded. */
     val isShadeFullyExpanded: Flow<Boolean>
 
-    /** Whether the Shade is fully collapsed. */
+    /** Whether Notifications Shade is expanded a non-zero amount. */
+    val isShadeAnyExpanded: StateFlow<Boolean>
+
+    /** Whether the Notifications Shade is fully collapsed. */
     val isShadeFullyCollapsed: Flow<Boolean>
 
     /**
@@ -102,7 +106,7 @@
      */
     val isAnyExpanded: StateFlow<Boolean>
 
-    /** The amount [0-1] that the shade has been opened. */
+    /** The amount [0-1] that the Notifications Shade has been opened. */
     val shadeExpansion: StateFlow<Float>
 
     /**
@@ -111,7 +115,7 @@
      */
     val qsExpansion: StateFlow<Float>
 
-    /** Whether Quick Settings is expanded a non-zero amount. */
+    /** Whether Quick Settings Shade is expanded a non-zero amount. */
     val isQsExpanded: StateFlow<Boolean>
 
     /**
@@ -142,16 +146,38 @@
     val isUserInteractingWithQs: Flow<Boolean>
 
     /**
-     * Triggers the expansion (opening) of the notification shade. If the notification shade is
-     * already open, this has no effect.
+     * Triggers the expansion (opening) of the notifications shade. If it is already expanded, this
+     * has no effect.
      */
-    fun expandNotificationShade(loggingReason: String)
+    fun expandNotificationsShade(loggingReason: String, transitionKey: TransitionKey? = null)
 
     /**
-     * Triggers the expansion (opening) of the quick settings shade. If the quick settings shade is
-     * already open, this has no effect.
+     * Triggers the expansion (opening) of the quick settings shade. If it is already expanded, this
+     * has no effect.
      */
-    fun expandQuickSettingsShade(loggingReason: String)
+    fun expandQuickSettingsShade(loggingReason: String, transitionKey: TransitionKey? = null)
+
+    /**
+     * Triggers the collapse (closing) of the notifications shade. If it is already collapsed, this
+     * has no effect.
+     */
+    fun collapseNotificationsShade(loggingReason: String, transitionKey: TransitionKey? = null)
+
+    /**
+     * Triggers the collapse (closing) of the quick settings shade. If it is already collapsed, this
+     * has no effect.
+     */
+    fun collapseQuickSettingsShade(
+        loggingReason: String,
+        transitionKey: TransitionKey? = null,
+        bypassNotificationsShade: Boolean = false,
+    )
+
+    /**
+     * Triggers the collapse (closing) of the notifications shade or quick settings shade, whichever
+     * is open. If both are already collapsed, this has no effect.
+     */
+    fun collapseEitherShade(loggingReason: String, transitionKey: TransitionKey? = null)
 }
 
 fun createAnyExpansionFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
index fb14828..322fca3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shade.domain.interactor
 
+import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.shade.shared.model.ShadeMode
 import javax.inject.Inject
@@ -31,6 +32,7 @@
     override val isShadeEnabled: StateFlow<Boolean> = inactiveFlowBoolean
     override val isQsEnabled: StateFlow<Boolean> = inactiveFlowBoolean
     override val shadeExpansion: StateFlow<Float> = inactiveFlowFloat
+    override val isShadeAnyExpanded: StateFlow<Boolean> = inactiveFlowBoolean
     override val qsExpansion: StateFlow<Float> = inactiveFlowFloat
     override val isQsExpanded: StateFlow<Boolean> = inactiveFlowBoolean
     override val isQsBypassingShade: Flow<Boolean> = inactiveFlowBoolean
@@ -50,7 +52,17 @@
 
     override fun getTopEdgeSplitFraction(): Float = 0.5f
 
-    override fun expandNotificationShade(loggingReason: String) {}
+    override fun expandNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {}
 
-    override fun expandQuickSettingsShade(loggingReason: String) {}
+    override fun expandQuickSettingsShade(loggingReason: String, transitionKey: TransitionKey?) {}
+
+    override fun collapseNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {}
+
+    override fun collapseQuickSettingsShade(
+        loggingReason: String,
+        transitionKey: TransitionKey?,
+        bypassNotificationsShade: Boolean,
+    ) {}
+
+    override fun collapseEitherShade(loggingReason: String, transitionKey: TransitionKey?) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
index 3eab02a..949d2aa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt
@@ -78,12 +78,16 @@
     override val isShadeFullyExpanded: Flow<Boolean> =
         baseShadeInteractor.shadeExpansion.map { it >= 1f }.distinctUntilChanged()
 
+    override val isShadeAnyExpanded: StateFlow<Boolean> =
+        baseShadeInteractor.shadeExpansion
+            .map { it > 0 }
+            .stateIn(scope, SharingStarted.Eagerly, false)
+
     override val isShadeFullyCollapsed: Flow<Boolean> =
         baseShadeInteractor.shadeExpansion.map { it <= 0f }.distinctUntilChanged()
 
     override val isUserInteracting: StateFlow<Boolean> =
         combine(isUserInteractingWithShade, isUserInteractingWithQs) { shade, qs -> shade || qs }
-            .distinctUntilChanged()
             .stateIn(scope, SharingStarted.Eagerly, false)
 
     override val isShadeTouchable: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
index df09486..0902c39 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorLegacyImpl.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.shade.domain.interactor
 
 import com.android.app.tracing.FlowTracing.traceAsCounter
+import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -111,18 +112,40 @@
     override val isUserInteractingWithQs: Flow<Boolean> =
         userInteractingFlow(repository.legacyQsTracking, repository.qsExpansion)
 
-    override fun expandNotificationShade(loggingReason: String) {
+    override fun expandNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {
         throw UnsupportedOperationException(
             "expandNotificationShade() is not supported in legacy shade"
         )
     }
 
-    override fun expandQuickSettingsShade(loggingReason: String) {
+    override fun expandQuickSettingsShade(loggingReason: String, transitionKey: TransitionKey?) {
         throw UnsupportedOperationException(
             "expandQuickSettingsShade() is not supported in legacy shade"
         )
     }
 
+    override fun collapseNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {
+        throw UnsupportedOperationException(
+            "collapseNotificationShade() is not supported in legacy shade"
+        )
+    }
+
+    override fun collapseQuickSettingsShade(
+        loggingReason: String,
+        transitionKey: TransitionKey?,
+        bypassNotificationsShade: Boolean,
+    ) {
+        throw UnsupportedOperationException(
+            "collapseQuickSettingsShade() is not supported in legacy shade"
+        )
+    }
+
+    override fun collapseEitherShade(loggingReason: String, transitionKey: TransitionKey?) {
+        throw UnsupportedOperationException(
+            "collapseEitherShade() is not supported in legacy shade"
+        )
+    }
+
     /**
      * Return a flow for whether a user is interacting with an expandable shade component using
      * tracking and expansion flows. NOTE: expansion must be a `StateFlow` to guarantee that
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index 81bf712..7658108 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -21,12 +21,16 @@
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.OverlayKey
 import com.android.compose.animation.scene.SceneKey
+import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Overlays
+import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.Instant
+import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import javax.inject.Inject
@@ -133,43 +137,120 @@
             }
         }
 
-    override fun expandNotificationShade(loggingReason: String) {
+    override fun expandNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {
         if (shadeModeInteractor.isDualShade) {
             if (Overlays.QuickSettingsShade in sceneInteractor.currentOverlays.value) {
                 sceneInteractor.replaceOverlay(
                     from = Overlays.QuickSettingsShade,
                     to = Overlays.NotificationsShade,
                     loggingReason = loggingReason,
+                    transitionKey = transitionKey,
                 )
             } else {
                 sceneInteractor.showOverlay(
                     overlay = Overlays.NotificationsShade,
                     loggingReason = loggingReason,
+                    transitionKey = transitionKey,
                 )
             }
         } else {
-            sceneInteractor.changeScene(toScene = Scenes.Shade, loggingReason = loggingReason)
+            sceneInteractor.changeScene(
+                toScene = Scenes.Shade,
+                loggingReason = loggingReason,
+                transitionKey =
+                    transitionKey ?: ToSplitShade.takeIf { shadeModeInteractor.isSplitShade },
+            )
         }
     }
 
-    override fun expandQuickSettingsShade(loggingReason: String) {
+    override fun expandQuickSettingsShade(loggingReason: String, transitionKey: TransitionKey?) {
         if (shadeModeInteractor.isDualShade) {
             if (Overlays.NotificationsShade in sceneInteractor.currentOverlays.value) {
                 sceneInteractor.replaceOverlay(
                     from = Overlays.NotificationsShade,
                     to = Overlays.QuickSettingsShade,
                     loggingReason = loggingReason,
+                    transitionKey = transitionKey,
                 )
             } else {
                 sceneInteractor.showOverlay(
                     overlay = Overlays.QuickSettingsShade,
                     loggingReason = loggingReason,
+                    transitionKey = transitionKey,
                 )
             }
         } else {
+            val isSplitShade = shadeModeInteractor.isSplitShade
             sceneInteractor.changeScene(
-                toScene = Scenes.QuickSettings,
+                toScene = if (isSplitShade) Scenes.Shade else Scenes.QuickSettings,
                 loggingReason = loggingReason,
+                transitionKey = transitionKey ?: ToSplitShade.takeIf { isSplitShade },
+            )
+        }
+    }
+
+    override fun collapseNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {
+        if (shadeModeInteractor.isDualShade) {
+            // TODO(b/356596436): Hide without animation if transitionKey is Instant.
+            sceneInteractor.hideOverlay(
+                overlay = Overlays.NotificationsShade,
+                loggingReason = loggingReason,
+                transitionKey = transitionKey,
+            )
+        } else if (transitionKey == Instant) {
+            // TODO(b/356596436): Define instant transition instead of snapToScene().
+            sceneInteractor.snapToScene(toScene = SceneFamilies.Home, loggingReason = loggingReason)
+        } else {
+            sceneInteractor.changeScene(
+                toScene = SceneFamilies.Home,
+                loggingReason = loggingReason,
+                transitionKey =
+                    transitionKey ?: ToSplitShade.takeIf { shadeModeInteractor.isSplitShade },
+            )
+        }
+    }
+
+    override fun collapseQuickSettingsShade(
+        loggingReason: String,
+        transitionKey: TransitionKey?,
+        bypassNotificationsShade: Boolean,
+    ) {
+        if (shadeModeInteractor.isDualShade) {
+            // TODO(b/356596436): Hide without animation if transitionKey is Instant.
+            sceneInteractor.hideOverlay(
+                overlay = Overlays.QuickSettingsShade,
+                loggingReason = loggingReason,
+                transitionKey = transitionKey,
+            )
+            return
+        }
+
+        val isSplitShade = shadeModeInteractor.isSplitShade
+        val targetScene =
+            if (bypassNotificationsShade || isSplitShade) SceneFamilies.Home else Scenes.Shade
+        if (transitionKey == Instant) {
+            // TODO(b/356596436): Define instant transition instead of snapToScene().
+            sceneInteractor.snapToScene(toScene = targetScene, loggingReason = loggingReason)
+        } else {
+            sceneInteractor.changeScene(
+                toScene = targetScene,
+                loggingReason = loggingReason,
+                transitionKey = transitionKey ?: ToSplitShade.takeIf { isSplitShade },
+            )
+        }
+    }
+
+    override fun collapseEitherShade(loggingReason: String, transitionKey: TransitionKey?) {
+        // Note: The notifications shade and QS shade may be both partially expanded simultaneously,
+        // so we don't use an 'else' clause here.
+        if (shadeExpansion.value > 0) {
+            collapseNotificationsShade(loggingReason = loggingReason, transitionKey = transitionKey)
+        }
+        if (isQsExpanded.value) {
+            collapseQuickSettingsShade(
+                loggingReason = loggingReason,
+                transitionKey = transitionKey,
+                bypassNotificationsShade = true,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
index 0fb3790..ea76ac4b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
@@ -21,10 +21,9 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.TransitionKeys.Instant
 import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -48,7 +47,7 @@
 
     @Deprecated("Use ShadeInteractor instead")
     override fun expandToNotifications() {
-        shadeInteractor.expandNotificationShade(
+        shadeInteractor.expandNotificationsShade(
             loggingReason = "ShadeLockscreenInteractorImpl.expandToNotifications"
         )
     }
@@ -71,17 +70,11 @@
     }
 
     override fun resetViews(animate: Boolean) {
-        val loggingReason = "ShadeLockscreenInteractorImpl.resetViews"
         // The existing comment to the only call to this claims it only calls it to collapse QS
-        if (shadeInteractor.shadeMode.value == ShadeMode.Dual) {
-            // TODO(b/356596436): Hide without animation if !animate.
-            sceneInteractor.hideOverlay(
-                overlay = Overlays.QuickSettingsShade,
-                loggingReason = loggingReason,
-            )
-        } else {
-            shadeInteractor.expandNotificationShade(loggingReason)
-        }
+        shadeInteractor.collapseQuickSettingsShade(
+            loggingReason = "ShadeLockscreenInteractorImpl.resetViews",
+            transitionKey = Instant.takeIf { !animate },
+        )
     }
 
     @Deprecated("Not supported by scenes")
@@ -93,7 +86,7 @@
         backgroundScope.launch {
             delay(delay)
             withContext(mainDispatcher) {
-                shadeInteractor.expandNotificationShade(
+                shadeInteractor.expandNotificationsShade(
                     "ShadeLockscreenInteractorImpl.transitionToExpandedShade"
                 )
             }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
index caa4513..c838c37 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt
@@ -55,6 +55,10 @@
     val isDualShade: Boolean
         get() = shadeMode.value is ShadeMode.Dual
 
+    /** Convenience shortcut for querying whether the current [shadeMode] is [ShadeMode.Split]. */
+    val isSplitShade: Boolean
+        get() = shadeMode.value is ShadeMode.Split
+
     /**
      * The fraction between [0..1] (i.e., percentage) of screen width to consider the threshold
      * between "top-left" and "top-right" for the purposes of dual-shade invocation.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index a154e91..bd4ed5b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -29,9 +29,7 @@
 import com.android.systemui.privacy.OngoingPrivacyChip
 import com.android.systemui.privacy.PrivacyItem
 import com.android.systemui.res.R
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.SceneFamilies
-import com.android.systemui.scene.shared.model.TransitionKeys
+import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse
 import com.android.systemui.shade.domain.interactor.PrivacyChipInteractor
 import com.android.systemui.shade.domain.interactor.ShadeHeaderClockInteractor
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -55,9 +53,8 @@
 class ShadeHeaderViewModel
 @AssistedInject
 constructor(
-    private val context: Context,
+    context: Context,
     private val activityStarter: ActivityStarter,
-    private val sceneInteractor: SceneInteractor,
     private val shadeInteractor: ShadeInteractor,
     private val mobileIconsInteractor: MobileIconsInteractor,
     val mobileIconsViewModel: MobileIconsViewModel,
@@ -120,7 +117,7 @@
                         map = { intent, _ ->
                             intent.action == Intent.ACTION_TIMEZONE_CHANGED ||
                                 intent.action == Intent.ACTION_LOCALE_CHANGED
-                        }
+                        },
                     )
                     .onEach { invalidateFormats -> updateDateTexts(invalidateFormats) }
                     .launchIn(this)
@@ -152,10 +149,9 @@
 
     /** Notifies that the system icons container was clicked. */
     fun onSystemIconContainerClicked() {
-        sceneInteractor.changeScene(
-            SceneFamilies.Home,
-            "ShadeHeaderViewModel.onSystemIconContainerClicked",
-            TransitionKeys.SlightlyFasterShadeCollapse,
+        shadeInteractor.collapseEitherShade(
+            loggingReason = "ShadeHeaderViewModel.onSystemIconContainerClicked",
+            transitionKey = SlightlyFasterShadeCollapse,
         )
     }
 
@@ -163,7 +159,7 @@
     fun onShadeCarrierGroupClicked() {
         activityStarter.postStartActivityDismissingKeyguard(
             Intent(Settings.ACTION_WIRELESS_SETTINGS),
-            0
+            0,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
index fe59d73..5ff5d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
@@ -45,17 +45,16 @@
 
 /**
  * How long to wait before auto-dismissing a notification that was kept for active remote input, and
- * has now sent a remote input. We auto-dismiss, because the app may not cannot cancel
- * these given that they technically don't exist anymore. We wait a bit in case the app issues
- * an update, and to also give the other lifetime extenders a beat to decide they want it.
+ * has now sent a remote input. We auto-dismiss, because the app may not cannot cancel these given
+ * that they technically don't exist anymore. We wait a bit in case the app issues an update, and to
+ * also give the other lifetime extenders a beat to decide they want it.
  */
 private const val REMOTE_INPUT_ACTIVE_EXTENDER_AUTO_CANCEL_DELAY: Long = 500
 
 /**
  * How long to wait before releasing a lifetime extension when requested to do so due to a user
- * interaction (such as tapping another action).
- * We wait a bit in case the app issues an update in response to the action, but not too long or we
- * risk appearing unresponsive to the user.
+ * interaction (such as tapping another action). We wait a bit in case the app issues an update in
+ * response to the action, but not too long or we risk appearing unresponsive to the user.
  */
 private const val REMOTE_INPUT_EXTENDER_RELEASE_DELAY: Long = 200
 
@@ -63,22 +62,21 @@
 private val DEBUG: Boolean by lazy { Log.isLoggable(TAG, Log.DEBUG) }
 
 @CoordinatorScope
-class RemoteInputCoordinator @Inject constructor(
+class RemoteInputCoordinator
+@Inject
+constructor(
     dumpManager: DumpManager,
     private val mRebuilder: RemoteInputNotificationRebuilder,
     private val mNotificationRemoteInputManager: NotificationRemoteInputManager,
     @Main private val mMainHandler: Handler,
-    private val mSmartReplyController: SmartReplyController
+    private val mSmartReplyController: SmartReplyController,
 ) : Coordinator, RemoteInputListener, Dumpable {
 
     @VisibleForTesting val mRemoteInputHistoryExtender = RemoteInputHistoryExtender()
     @VisibleForTesting val mSmartReplyHistoryExtender = SmartReplyHistoryExtender()
     @VisibleForTesting val mRemoteInputActiveExtender = RemoteInputActiveExtender()
-    private val mRemoteInputLifetimeExtenders = listOf(
-            mRemoteInputHistoryExtender,
-            mSmartReplyHistoryExtender,
-            mRemoteInputActiveExtender
-    )
+    private val mRemoteInputLifetimeExtenders =
+        listOf(mRemoteInputHistoryExtender, mSmartReplyHistoryExtender, mRemoteInputActiveExtender)
 
     private lateinit var mNotifUpdater: InternalNotifUpdater
 
@@ -93,9 +91,7 @@
         if (lifetimeExtensionRefactor()) {
             pipeline.addNotificationLifetimeExtender(mRemoteInputActiveExtender)
         } else {
-            mRemoteInputLifetimeExtenders.forEach {
-                pipeline.addNotificationLifetimeExtender(it)
-            }
+            mRemoteInputLifetimeExtenders.forEach { pipeline.addNotificationLifetimeExtender(it) }
         }
         mNotifUpdater = pipeline.getInternalNotifUpdater(TAG)
         pipeline.addCollectionListener(mCollectionListener)
@@ -105,65 +101,86 @@
      * Listener that updates the appearance of the notification if it has been lifetime extended
      * by a a direct reply or a smart reply, and cancelled.
      */
-    val mCollectionListener = object : NotifCollectionListener {
-        override fun onEntryUpdated(entry: NotificationEntry, fromSystem: Boolean) {
-            if (DEBUG) {
-                Log.d(TAG, "mCollectionListener.onEntryUpdated(entry=${entry.key}," +
-                        " fromSystem=$fromSystem)")
-            }
-            if (fromSystem) {
-                if (lifetimeExtensionRefactor()) {
-                    if ((entry.getSbn().getNotification().flags
-                                    and FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
-                        if (mNotificationRemoteInputManager.shouldKeepForRemoteInputHistory(
-                                        entry)) {
-                            val newSbn = mRebuilder.rebuildForRemoteInputReply(entry)
-                            entry.onRemoteInputInserted()
-                            mNotifUpdater.onInternalNotificationUpdate(newSbn,
-                                    "Extending lifetime of notification with remote input")
-                        } else if (mNotificationRemoteInputManager.shouldKeepForSmartReplyHistory(
-                                        entry)) {
-                            val newSbn = mRebuilder.rebuildForCanceledSmartReplies(entry)
-                            mSmartReplyController.stopSending(entry)
-                            mNotifUpdater.onInternalNotificationUpdate(newSbn,
-                                    "Extending lifetime of notification with smart reply")
-                        } else {
-                            // The app may have re-cancelled a notification after it had already
-                            // been lifetime extended.
-                            // Rebuild the notification with the replies it already had to ensure
-                            // those replies continue to be displayed.
-                            val newSbn = mRebuilder.rebuildWithExistingReplies(entry)
-                            mNotifUpdater.onInternalNotificationUpdate(newSbn,
+    val mCollectionListener =
+        object : NotifCollectionListener {
+            override fun onEntryUpdated(entry: NotificationEntry, fromSystem: Boolean) {
+                if (DEBUG) {
+                    Log.d(
+                        TAG,
+                        "mCollectionListener.onEntryUpdated(entry=${entry.key}," +
+                            " fromSystem=$fromSystem)",
+                    )
+                }
+                if (fromSystem) {
+                    if (lifetimeExtensionRefactor()) {
+                        if (
+                            (entry.getSbn().getNotification().flags and
+                                FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0
+                        ) {
+                            if (
+                                mNotificationRemoteInputManager.shouldKeepForRemoteInputHistory(
+                                    entry
+                                )
+                            ) {
+                                val newSbn = mRebuilder.rebuildForRemoteInputReply(entry)
+                                entry.onRemoteInputInserted()
+                                mNotifUpdater.onInternalNotificationUpdate(
+                                    newSbn,
+                                    "Extending lifetime of notification with remote input",
+                                )
+                            } else if (
+                                mNotificationRemoteInputManager.shouldKeepForSmartReplyHistory(
+                                    entry
+                                )
+                            ) {
+                                val newSbn = mRebuilder.rebuildForCanceledSmartReplies(entry)
+                                mSmartReplyController.stopSending(entry)
+                                mNotifUpdater.onInternalNotificationUpdate(
+                                    newSbn,
+                                    "Extending lifetime of notification with smart reply",
+                                )
+                            } else {
+                                // The app may have re-cancelled a notification after it had already
+                                // been lifetime extended.
+                                // Rebuild the notification with the replies it already had to
+                                // ensure
+                                // those replies continue to be displayed.
+                                val newSbn = mRebuilder.rebuildWithExistingReplies(entry)
+                                mNotifUpdater.onInternalNotificationUpdate(
+                                    newSbn,
                                     "Extending lifetime of notification that has already been " +
-                                            "lifetime extended.")
+                                        "lifetime extended.",
+                                )
+                            }
+                        } else {
+                            // Notifications updated without FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY
+                            // should have their remote inputs list cleared.
+                            entry.remoteInputs = null
                         }
                     } else {
-                        // Notifications updated without FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY
-                        // should have their remote inputs list cleared.
-                        entry.remoteInputs = null
+                        // Mark smart replies as sent whenever a notification is updated by the app,
+                        // otherwise the smart replies are never marked as sent.
+                        mSmartReplyController.stopSending(entry)
                     }
-                } else {
-                    // Mark smart replies as sent whenever a notification is updated by the app,
-                    // otherwise the smart replies are never marked as sent.
-                    mSmartReplyController.stopSending(entry)
+                }
+            }
+
+            override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+                if (DEBUG) Log.d(TAG, "mCollectionListener.onEntryRemoved(entry=${entry.key})")
+                // We're removing the notification, the smart reply controller can forget about it.
+                // TODO(b/145659174): track 'sending' state on the entry to avoid having to clear
+                // it.
+                mSmartReplyController.stopSending(entry)
+
+                // When we know the entry will not be lifetime extended, clean up the remote input
+                // view
+                // TODO: Share code with NotifCollection.cannotBeLifetimeExtended
+                if (reason == REASON_CANCEL || reason == REASON_CLICK) {
+                    mNotificationRemoteInputManager.cleanUpRemoteInputForUserRemoval(entry)
                 }
             }
         }
 
-        override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
-            if (DEBUG) Log.d(TAG, "mCollectionListener.onEntryRemoved(entry=${entry.key})")
-            // We're removing the notification, the smart reply controller can forget about it.
-            // TODO(b/145659174): track 'sending' state on the entry to avoid having to clear it.
-            mSmartReplyController.stopSending(entry)
-
-            // When we know the entry will not be lifetime extended, clean up the remote input view
-            // TODO: Share code with NotifCollection.cannotBeLifetimeExtended
-            if (reason == REASON_CANCEL || reason == REASON_CLICK) {
-                mNotificationRemoteInputManager.cleanUpRemoteInputForUserRemoval(entry)
-            }
-        }
-    }
-
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         mRemoteInputLifetimeExtenders.forEach { it.dump(pw, args) }
     }
@@ -183,22 +200,25 @@
         // view it is already canceled, so we'll need to cancel it on the apps behalf
         // now that a reply has been sent. However, delay so that the app has time to posts an
         // update in the mean time, and to give another lifetime extender time to pick it up.
-        mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay(entry.key,
-                REMOTE_INPUT_ACTIVE_EXTENDER_AUTO_CANCEL_DELAY)
+        mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay(
+            entry.key,
+            REMOTE_INPUT_ACTIVE_EXTENDER_AUTO_CANCEL_DELAY,
+        )
     }
 
     private fun onSmartReplySent(entry: NotificationEntry, reply: CharSequence) {
         if (DEBUG) Log.d(TAG, "onSmartReplySent(entry=${entry.key})")
         val newSbn = mRebuilder.rebuildForSendingSmartReply(entry, reply)
-        mNotifUpdater.onInternalNotificationUpdate(newSbn,
-                "Adding smart reply spinner for sent")
+        mNotifUpdater.onInternalNotificationUpdate(newSbn, "Adding smart reply spinner for sent")
 
         // If we're extending for remote input being active, then from the apps point of
         // view it is already canceled, so we'll need to cancel it on the apps behalf
         // now that a reply has been sent. However, delay so that the app has time to posts an
         // update in the mean time, and to give another lifetime extender time to pick it up.
-        mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay(entry.key,
-                REMOTE_INPUT_ACTIVE_EXTENDER_AUTO_CANCEL_DELAY)
+        mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay(
+            entry.key,
+            REMOTE_INPUT_ACTIVE_EXTENDER_AUTO_CANCEL_DELAY,
+        )
     }
 
     override fun onPanelCollapsed() {
@@ -208,19 +228,25 @@
     override fun isNotificationKeptForRemoteInputHistory(key: String) =
         if (!lifetimeExtensionRefactor()) {
             mRemoteInputHistoryExtender.isExtending(key) ||
-                    mSmartReplyHistoryExtender.isExtending(key)
+                mSmartReplyHistoryExtender.isExtending(key)
         } else false
 
     override fun releaseNotificationIfKeptForRemoteInputHistory(entry: NotificationEntry) {
         if (DEBUG) Log.d(TAG, "releaseNotificationIfKeptForRemoteInputHistory(entry=${entry.key})")
         if (!lifetimeExtensionRefactor()) {
-            mRemoteInputHistoryExtender.endLifetimeExtensionAfterDelay(entry.key,
-                    REMOTE_INPUT_EXTENDER_RELEASE_DELAY)
-            mSmartReplyHistoryExtender.endLifetimeExtensionAfterDelay(entry.key,
-                    REMOTE_INPUT_EXTENDER_RELEASE_DELAY)
+            mRemoteInputHistoryExtender.endLifetimeExtensionAfterDelay(
+                entry.key,
+                REMOTE_INPUT_EXTENDER_RELEASE_DELAY,
+            )
+            mSmartReplyHistoryExtender.endLifetimeExtensionAfterDelay(
+                entry.key,
+                REMOTE_INPUT_EXTENDER_RELEASE_DELAY,
+            )
         }
-        mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay(entry.key,
-                REMOTE_INPUT_EXTENDER_RELEASE_DELAY)
+        mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay(
+            entry.key,
+            REMOTE_INPUT_EXTENDER_RELEASE_DELAY,
+        )
     }
 
     override fun setRemoteInputController(remoteInputController: RemoteInputController) {
@@ -229,32 +255,36 @@
 
     @VisibleForTesting
     inner class RemoteInputHistoryExtender :
-            SelfTrackingLifetimeExtender(TAG, "RemoteInputHistory", DEBUG, mMainHandler) {
+        SelfTrackingLifetimeExtender(TAG, "RemoteInputHistory", DEBUG, mMainHandler) {
 
         override fun queryShouldExtendLifetime(entry: NotificationEntry): Boolean =
-                mNotificationRemoteInputManager.shouldKeepForRemoteInputHistory(entry)
+            mNotificationRemoteInputManager.shouldKeepForRemoteInputHistory(entry)
 
         override fun onStartedLifetimeExtension(entry: NotificationEntry) {
             val newSbn = mRebuilder.rebuildForRemoteInputReply(entry)
             entry.onRemoteInputInserted()
-            mNotifUpdater.onInternalNotificationUpdate(newSbn,
-                    "Extending lifetime of notification with remote input")
+            mNotifUpdater.onInternalNotificationUpdate(
+                newSbn,
+                "Extending lifetime of notification with remote input",
+            )
             // TODO: Check if the entry was removed due perhaps to an inflation exception?
         }
     }
 
     @VisibleForTesting
     inner class SmartReplyHistoryExtender :
-            SelfTrackingLifetimeExtender(TAG, "SmartReplyHistory", DEBUG, mMainHandler) {
+        SelfTrackingLifetimeExtender(TAG, "SmartReplyHistory", DEBUG, mMainHandler) {
 
         override fun queryShouldExtendLifetime(entry: NotificationEntry): Boolean =
-                mNotificationRemoteInputManager.shouldKeepForSmartReplyHistory(entry)
+            mNotificationRemoteInputManager.shouldKeepForSmartReplyHistory(entry)
 
         override fun onStartedLifetimeExtension(entry: NotificationEntry) {
             val newSbn = mRebuilder.rebuildForCanceledSmartReplies(entry)
             mSmartReplyController.stopSending(entry)
-            mNotifUpdater.onInternalNotificationUpdate(newSbn,
-                    "Extending lifetime of notification with smart reply")
+            mNotifUpdater.onInternalNotificationUpdate(
+                newSbn,
+                "Extending lifetime of notification with smart reply",
+            )
             // TODO: Check if the entry was removed due perhaps to an inflation exception?
         }
 
@@ -266,9 +296,9 @@
 
     @VisibleForTesting
     inner class RemoteInputActiveExtender :
-            SelfTrackingLifetimeExtender(TAG, "RemoteInputActive", DEBUG, mMainHandler) {
+        SelfTrackingLifetimeExtender(TAG, "RemoteInputActive", DEBUG, mMainHandler) {
 
         override fun queryShouldExtendLifetime(entry: NotificationEntry): Boolean =
-                mNotificationRemoteInputManager.isRemoteInputActive(entry)
+            mNotificationRemoteInputManager.isRemoteInputActive(entry)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 1431b28..d246b04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -88,6 +88,8 @@
     private ExpandableView mLastVisibleBackgroundChild;
     private float mCurrentScrollVelocity;
     private int mStatusBarState;
+    private boolean mShowingStackOnLockscreen;
+    private float mLockscreenStackFadeInProgress;
     private float mExpandingVelocity;
     private boolean mPanelTracking;
     private boolean mExpansionChanging;
@@ -624,6 +626,26 @@
         return mStatusBarState == StatusBarState.KEYGUARD;
     }
 
+    public boolean isShowingStackOnLockscreen() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return false;
+        return mShowingStackOnLockscreen;
+    }
+
+    public void setShowingStackOnLockscreen(boolean showingStackOnLockscreen) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        mShowingStackOnLockscreen = showingStackOnLockscreen;
+    }
+
+    public float getLockscreenStackFadeInProgress() {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return 0f;
+        return mLockscreenStackFadeInProgress;
+    }
+
+    public void setLockscreenStackFadeInProgress(float lockscreenStackFadeInProgress) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        mLockscreenStackFadeInProgress = lockscreenStackFadeInProgress;
+    }
+
     public void setStatusBarState(int statusBarState) {
         if (mStatusBarState != StatusBarState.KEYGUARD) {
             mIsFlingRequiredAfterLockScreenSwipeUp = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index b2b2c2a..0a44a2b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -5342,6 +5342,19 @@
         updateDismissBehavior();
     }
 
+    @Override
+    public void setShowingStackOnLockscreen(boolean showingStackOnLockscreen) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        mAmbientState.setShowingStackOnLockscreen(showingStackOnLockscreen);
+    }
+
+    @Override
+    public void setAlphaForLockscreenFadeIn(float alphaForLockscreenFadeIn) {
+        if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+        mAmbientState.setLockscreenStackFadeInProgress(alphaForLockscreenFadeIn);
+        requestChildrenUpdate();
+    }
+
     void setUpcomingStatusBarState(int upcomingStatusBarState) {
         FooterViewRefactor.assertInLegacyMode();
         mUpcomingStatusBarState = upcomingStatusBarState;
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 dad6894..9c5fecf 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
@@ -380,7 +380,7 @@
             new StatusBarStateController.StateListener() {
                 @Override
                 public void onStatePreChange(int oldState, int newState) {
-                    if (oldState == StatusBarState.SHADE_LOCKED
+                    if (!SceneContainerFlag.isEnabled() && oldState == StatusBarState.SHADE_LOCKED
                             && newState == KEYGUARD) {
                         mView.requestAnimateEverything();
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 9c0fd0e..e0b0ccd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -148,12 +148,18 @@
             if (isHunGoingToShade) {
                 // Keep 100% opacity for heads up notification going to shade.
                 viewState.setAlpha(1f);
-            } else if (ambientState.isOnKeyguard()) {
+            } else if ((!SceneContainerFlag.isEnabled() && ambientState.isOnKeyguard())
+                    || ambientState.isShowingStackOnLockscreen()) {
                 // Adjust alpha for wakeup to lockscreen.
                 if (view.isHeadsUpState()) {
                     // Pulsing HUN should be visible on AOD and stay visible during 
                     // AOD=>lockscreen transition
                     viewState.setAlpha(1f - ambientState.getHideAmount());
+                } else if (SceneContainerFlag.isEnabled()) {
+                    // Take into account scene container-specific Lockscreen fade-in progress
+                    float fadeAlpha = ambientState.getLockscreenStackFadeInProgress();
+                    float dozeAlpha = 1f - ambientState.getDozeAmount();
+                    viewState.setAlpha(Math.min(dozeAlpha, fadeAlpha));
                 } else {
                     // Normal notifications are hidden on AOD and should fade in during 
                     // AOD=>lockscreen transition
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
index f6722a4..c0f1a56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
@@ -31,6 +31,9 @@
     /** The alpha of the shade in order to show brightness. */
     val alphaForBrightnessMirror = MutableStateFlow(1f)
 
+    /** The alpha of the Notification Stack for lockscreen fade-in */
+    val alphaForLockscreenFadeIn = MutableStateFlow(0f)
+
     /**
      * The bounds of the notification shade scrim / container in the current scene.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 756cd87..32e092b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -56,10 +56,9 @@
 
     /** The rounding of the notification stack. */
     val shadeScrimRounding: Flow<ShadeScrimRounding> =
-        combine(
-                shadeInteractor.shadeMode,
-                isExpandingFromHeadsUp,
-            ) { shadeMode, isExpandingFromHeadsUp ->
+        combine(shadeInteractor.shadeMode, isExpandingFromHeadsUp) {
+                shadeMode,
+                isExpandingFromHeadsUp ->
                 ShadeScrimRounding(
                     isTopRounded = !(shadeMode == ShadeMode.Split && isExpandingFromHeadsUp),
                     isBottomRounded = shadeMode != ShadeMode.Single,
@@ -71,6 +70,10 @@
     val alphaForBrightnessMirror: StateFlow<Float> =
         placeholderRepository.alphaForBrightnessMirror.asStateFlow()
 
+    /** The alpha of the Notification Stack for lockscreen fade-in */
+    val alphaForLockscreenFadeIn: StateFlow<Float> =
+        placeholderRepository.alphaForLockscreenFadeIn.asStateFlow()
+
     /** The height of the keyguard's available space bounds */
     val constrainedAvailableSpace: StateFlow<Int> =
         placeholderRepository.constrainedAvailableSpace.asStateFlow()
@@ -99,7 +102,7 @@
     val shouldCloseGuts: Flow<Boolean> =
         combine(
             sceneInteractor.isSceneContainerUserInputOngoing,
-            viewHeightRepository.isCurrentGestureInGuts
+            viewHeightRepository.isCurrentGestureInGuts,
         ) { isUserInputOngoing, isCurrentGestureInGuts ->
             isUserInputOngoing && !isCurrentGestureInGuts
         }
@@ -109,6 +112,11 @@
         placeholderRepository.alphaForBrightnessMirror.value = alpha
     }
 
+    /** Sets the alpha to apply to the NSSL for fade-in on lockscreen */
+    fun setAlphaForLockscreenFadeIn(alpha: Float) {
+        placeholderRepository.alphaForLockscreenFadeIn.value = alpha
+    }
+
     /** Sets the position of the notification stack in the current scene. */
     fun setShadeScrimBounds(bounds: ShadeScrimBounds?) {
         check(bounds == null || bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index 41c0293..0113e36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -89,6 +89,12 @@
     /** sets the current QS expand fraction */
     fun setQsExpandFraction(expandFraction: Float)
 
+    /** set whether we are idle on the lockscreen scene */
+    fun setShowingStackOnLockscreen(showingStackOnLockscreen: Boolean)
+
+    /** set the alpha from 0-1f of stack fade-in on lockscreen */
+    fun setAlphaForLockscreenFadeIn(alphaForLockscreenFadeIn: Float)
+
     /** Sets whether the view is displayed in doze mode. */
     fun setDozing(dozing: Boolean)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index 2e37dea..99ff678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -88,6 +88,14 @@
                 viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) }
             }
             launch { viewModel.qsExpandFraction.collect { view.setQsExpandFraction(it) } }
+            launch {
+                viewModel.isShowingStackOnLockscreen.collect {
+                    view.setShowingStackOnLockscreen(it)
+                }
+            }
+            launch {
+                viewModel.alphaForLockscreenFadeIn.collect { view.setAlphaForLockscreenFadeIn(it) }
+            }
             launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } }
             launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } }
             launch {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLockscreenScrimViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLockscreenScrimViewModel.kt
new file mode 100644
index 0000000..84aa997
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLockscreenScrimViewModel.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
+import com.android.systemui.util.kotlin.ActivatableFlowDumper
+import com.android.systemui.util.kotlin.ActivatableFlowDumperImpl
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+class NotificationLockscreenScrimViewModel
+@AssistedInject
+constructor(
+    dumpManager: DumpManager,
+    shadeInteractor: ShadeInteractor,
+    private val stackAppearanceInteractor: NotificationStackAppearanceInteractor,
+) :
+    ActivatableFlowDumper by ActivatableFlowDumperImpl(dumpManager, "NotificationScrollViewModel"),
+    ExclusiveActivatable() {
+
+    val shadeMode = shadeInteractor.shadeMode
+
+    /** Sets the alpha to apply to the NSSL for fade-in on lockscreen */
+    fun setAlphaForLockscreenFadeIn(alpha: Float) {
+        stackAppearanceInteractor.setAlphaForLockscreenFadeIn(alpha)
+    }
+
+    override suspend fun onActivated(): Nothing {
+        activateFlowDumper()
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): NotificationLockscreenScrimViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 5b2e02d..cd9c07e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -82,8 +82,13 @@
     private fun fullyExpandedDuringSceneChange(change: ChangeScene): Boolean {
         // The lockscreen stack is visible during all transitions away from the lockscreen, so keep
         // the stack expanded until those transitions finish.
-        return (expandedInScene(change.fromScene) && expandedInScene(change.toScene)) ||
-            change.isBetween({ it == Scenes.Lockscreen }, { true })
+        return if (change.isFrom({ it == Scenes.Lockscreen }, to = { true })) {
+            true
+        } else if (change.isFrom({ it == Scenes.Shade }, to = { it == Scenes.Lockscreen })) {
+            false
+        } else {
+            (expandedInScene(change.fromScene) && expandedInScene(change.toScene))
+        }
     }
 
     private fun expandFractionDuringSceneChange(
@@ -93,7 +98,10 @@
     ): Float {
         return if (fullyExpandedDuringSceneChange(change)) {
             1f
-        } else if (change.isBetween({ it == Scenes.Gone }, { it == Scenes.Shade })) {
+        } else if (
+            change.isBetween({ it == Scenes.Gone }, { it == Scenes.Shade }) ||
+                change.isFrom({ it == Scenes.Shade }, to = { it == Scenes.Lockscreen })
+        ) {
             shadeExpansion
         } else if (change.isBetween({ it == Scenes.Gone }, { it == Scenes.QuickSettings })) {
             // during QS expansion, increase fraction at same rate as scrim alpha,
@@ -178,6 +186,18 @@
             .distinctUntilChanged()
             .dumpWhileCollecting("shouldResetStackTop")
 
+    /** Whether the Notification Stack is visibly on the lockscreen scene. */
+    val isShowingStackOnLockscreen: Flow<Boolean> =
+        sceneInteractor.transitionState
+            .mapNotNull { state ->
+                state.isIdle(Scenes.Lockscreen) ||
+                    state.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Shade)
+            }
+            .distinctUntilChanged()
+
+    /** The alpha of the Notification Stack for lockscreen fade-in */
+    val alphaForLockscreenFadeIn = stackAppearanceInteractor.alphaForLockscreenFadeIn
+
     private operator fun SceneKey.contains(scene: SceneKey) =
         sceneInteractor.isSceneInFamily(scene, this)
 
@@ -298,3 +318,6 @@
 
 private fun ChangeScene.isBetween(a: (SceneKey) -> Boolean, b: (SceneKey) -> Boolean): Boolean =
     (a(fromScene) && b(toScene)) || (b(fromScene) && a(toScene))
+
+private fun ChangeScene.isFrom(from: (SceneKey) -> Boolean, to: (SceneKey) -> Boolean): Boolean =
+    from(fromScene) && to(toScene)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
similarity index 100%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
index a1750cd..b1ec740 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.OverlayKey
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
@@ -28,6 +29,7 @@
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.data.repository.setSceneTransition
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Overlays
 import com.android.systemui.scene.shared.model.Scenes
@@ -38,9 +40,9 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
+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
 
@@ -55,14 +57,9 @@
     private val configurationRepository = kosmos.fakeConfigurationRepository
     private val keyguardRepository = kosmos.fakeKeyguardRepository
     private val sceneInteractor = kosmos.sceneInteractor
-    private val shadeTestUtil = kosmos.shadeTestUtil
+    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }
 
-    private lateinit var underTest: ShadeInteractorSceneContainerImpl
-
-    @Before
-    fun setUp() {
-        underTest = kosmos.shadeInteractorSceneContainerImpl
-    }
+    private val underTest by lazy { kosmos.shadeInteractorSceneContainerImpl }
 
     @Test
     fun qsExpansionWhenInSplitShadeAndQsExpanded() =
@@ -600,14 +597,14 @@
 
     @Test
     @EnableFlags(DualShade.FLAG_NAME)
-    fun expandNotificationShade_dualShadeEnabled_opensOverlay() =
+    fun expandNotificationsShade_dualShade_opensOverlay() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene)
             val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
             assertThat(currentOverlays).isEmpty()
 
-            underTest.expandNotificationShade("reason")
+            underTest.expandNotificationsShade("reason")
 
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
             assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade)
@@ -615,14 +612,15 @@
 
     @Test
     @DisableFlags(DualShade.FLAG_NAME)
-    fun expandNotificationShade_dualShadeDisabled_switchesToShadeScene() =
+    fun expandNotificationsShade_singleShade_switchesToShadeScene() =
         testScope.runTest {
+            shadeTestUtil.setSplitShade(false)
             val currentScene by collectLastValue(sceneInteractor.currentScene)
             val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
             assertThat(currentOverlays).isEmpty()
 
-            underTest.expandNotificationShade("reason")
+            underTest.expandNotificationsShade("reason")
 
             assertThat(currentScene).isEqualTo(Scenes.Shade)
             assertThat(currentOverlays).isEmpty()
@@ -630,7 +628,7 @@
 
     @Test
     @EnableFlags(DualShade.FLAG_NAME)
-    fun expandNotificationShade_dualShadeEnabledAndQuickSettingsOpen_replacesOverlay() =
+    fun expandNotificationsShade_dualShadeQuickSettingsOpen_replacesOverlay() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene)
             val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
@@ -638,14 +636,14 @@
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
             assertThat(currentOverlays).containsExactly(Overlays.QuickSettingsShade)
 
-            underTest.expandNotificationShade("reason")
+            underTest.expandNotificationsShade("reason")
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
             assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade)
         }
 
     @Test
     @EnableFlags(DualShade.FLAG_NAME)
-    fun expandQuickSettingsShade_dualShadeEnabled_opensOverlay() =
+    fun expandQuickSettingsShade_dualShade_opensOverlay() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene)
             val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
@@ -660,8 +658,9 @@
 
     @Test
     @DisableFlags(DualShade.FLAG_NAME)
-    fun expandQuickSettingsShade_dualShadeDisabled_switchesToQuickSettingsScene() =
+    fun expandQuickSettingsShade_singleShade_switchesToQuickSettingsScene() =
         testScope.runTest {
+            shadeTestUtil.setSplitShade(false)
             val currentScene by collectLastValue(sceneInteractor.currentScene)
             val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
@@ -674,12 +673,28 @@
         }
 
     @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun expandQuickSettingsShade_splitShade_switchesToShadeScene() =
+        testScope.runTest {
+            shadeTestUtil.setSplitShade(true)
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).isEmpty()
+
+            underTest.expandQuickSettingsShade("reason")
+
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+            assertThat(currentOverlays).isEmpty()
+        }
+
+    @Test
     @EnableFlags(DualShade.FLAG_NAME)
-    fun expandQuickSettingsShade_dualShadeEnabledAndNotificationsOpen_replacesOverlay() =
+    fun expandQuickSettingsShade_dualShadeNotificationsOpen_replacesOverlay() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.currentScene)
             val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
-            underTest.expandNotificationShade("reason")
+            underTest.expandNotificationsShade("reason")
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
             assertThat(currentOverlays).containsExactly(Overlays.NotificationsShade)
 
@@ -687,4 +702,141 @@
             assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
             assertThat(currentOverlays).containsExactly(Overlays.QuickSettingsShade)
         }
+
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun collapseNotificationsShade_dualShade_hidesOverlay() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            openShade(Overlays.NotificationsShade)
+
+            underTest.collapseNotificationsShade("reason")
+
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).isEmpty()
+        }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun collapseNotificationsShade_singleShade_switchesToLockscreen() =
+        testScope.runTest {
+            shadeTestUtil.setSplitShade(false)
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            sceneInteractor.changeScene(Scenes.Shade, "reason")
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+            assertThat(currentOverlays).isEmpty()
+
+            underTest.collapseNotificationsShade("reason")
+
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).isEmpty()
+        }
+
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun collapseQuickSettingsShade_dualShade_hidesOverlay() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            openShade(Overlays.QuickSettingsShade)
+
+            underTest.collapseQuickSettingsShade("reason")
+
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).isEmpty()
+        }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun collapseQuickSettingsShadeNotBypassingShade_singleShade_switchesToShade() =
+        testScope.runTest {
+            shadeTestUtil.setSplitShade(false)
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
+            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+            assertThat(currentOverlays).isEmpty()
+
+            underTest.collapseQuickSettingsShade(
+                loggingReason = "reason",
+                bypassNotificationsShade = false,
+            )
+
+            assertThat(currentScene).isEqualTo(Scenes.Shade)
+            assertThat(currentOverlays).isEmpty()
+        }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun collapseQuickSettingsShadeNotBypassingShade_splitShade_switchesToLockscreen() =
+        testScope.runTest {
+            shadeTestUtil.setSplitShade(true)
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
+            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+            assertThat(currentOverlays).isEmpty()
+
+            underTest.collapseQuickSettingsShade(
+                loggingReason = "reason",
+                bypassNotificationsShade = false,
+            )
+
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).isEmpty()
+        }
+
+    @Test
+    @DisableFlags(DualShade.FLAG_NAME)
+    fun collapseQuickSettingsShadeBypassingShade_singleShade_switchesToLockscreen() =
+        testScope.runTest {
+            shadeTestUtil.setSplitShade(false)
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            sceneInteractor.changeScene(Scenes.QuickSettings, "reason")
+            assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+            assertThat(currentOverlays).isEmpty()
+
+            underTest.collapseQuickSettingsShade(
+                loggingReason = "reason",
+                bypassNotificationsShade = true,
+            )
+
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).isEmpty()
+        }
+
+    @Test
+    @EnableFlags(DualShade.FLAG_NAME)
+    fun collapseEitherShade_dualShade_hidesBothOverlays() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+            openShade(Overlays.QuickSettingsShade)
+            openShade(Overlays.NotificationsShade)
+            assertThat(currentOverlays)
+                .containsExactly(Overlays.QuickSettingsShade, Overlays.NotificationsShade)
+
+            underTest.collapseEitherShade("reason")
+
+            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+            assertThat(currentOverlays).isEmpty()
+        }
+
+    private fun TestScope.openShade(overlay: OverlayKey) {
+        val isAnyExpanded by collectLastValue(underTest.isAnyExpanded)
+        val currentScene by collectLastValue(sceneInteractor.currentScene)
+        val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+        val initialScene = checkNotNull(currentScene)
+        sceneInteractor.showOverlay(overlay, "reason")
+        kosmos.setSceneTransition(
+            ObservableTransitionState.Idle(initialScene, checkNotNull(currentOverlays))
+        )
+        runCurrent()
+        assertThat(currentScene).isEqualTo(initialScene)
+        assertThat(currentOverlays).contains(overlay)
+        assertThat(isAnyExpanded).isTrue()
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
index ff8b478..a80a409 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
@@ -17,13 +17,13 @@
 package com.android.systemui.qs.ui.viewmodel
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory
 
 val Kosmos.quickSettingsShadeOverlayContentViewModel: QuickSettingsShadeOverlayContentViewModel by
     Kosmos.Fixture {
         QuickSettingsShadeOverlayContentViewModel(
-            sceneInteractor = sceneInteractor,
+            shadeInteractor = shadeInteractor,
             shadeHeaderViewModelFactory = shadeHeaderViewModelFactory,
             quickSettingsContainerViewModel = quickSettingsContainerViewModel,
         )
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
index 6d488d2..60141c6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeTestUtil.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.shade.data.repository.ShadeRepository
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.TestScope
@@ -133,7 +134,7 @@
 class ShadeTestUtilLegacyImpl(
     val testScope: TestScope,
     val shadeRepository: FakeShadeRepository,
-    val context: SysuiTestableContext
+    val context: SysuiTestableContext,
 ) : ShadeTestUtilDelegate {
     override fun setShadeAndQsExpansion(shadeExpansion: Float, qsExpansion: Float) {
         shadeRepository.setLegacyShadeExpansion(shadeExpansion)
@@ -191,6 +192,7 @@
 }
 
 /** Sets up shade state for tests when the scene container flag is enabled. */
+@OptIn(ExperimentalCoroutinesApi::class)
 class ShadeTestUtilSceneImpl(
     val testScope: TestScope,
     val sceneInteractor: SceneInteractor,
@@ -269,7 +271,7 @@
         from: SceneKey,
         to: SceneKey,
         progress: Float,
-        isInitiatedByUserInput: Boolean = true
+        isInitiatedByUserInput: Boolean = true,
     ) {
         sceneInteractor.changeScene(from, "test")
         val transitionState =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
index 9cdd519..7a15fdf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayContentViewModel
-import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModelFactory
 
 val Kosmos.notificationsShadeOverlayContentViewModel:
@@ -27,6 +27,6 @@
     NotificationsShadeOverlayContentViewModel(
         shadeHeaderViewModelFactory = shadeHeaderViewModelFactory,
         notificationsPlaceholderViewModelFactory = notificationsPlaceholderViewModelFactory,
-        sceneInteractor = sceneInteractor,
+        shadeInteractor = shadeInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
index 7eb9f34..f5b856d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelKosmos.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.broadcast.broadcastDispatcher
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.plugins.activityStarter
-import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.shade.domain.interactor.privacyChipInteractor
 import com.android.systemui.shade.domain.interactor.shadeHeaderClockInteractor
 import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -32,7 +31,6 @@
         ShadeHeaderViewModel(
             context = applicationContext,
             activityStarter = activityStarter,
-            sceneInteractor = sceneInteractor,
             shadeInteractor = shadeInteractor,
             mobileIconsInteractor = mobileIconsInteractor,
             mobileIconsViewModel = mobileIconsViewModel,
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 3224b27..73b7b35 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -165,16 +165,27 @@
     protected final AccessibilitySecurityPolicy mSecurityPolicy;
     protected final AccessibilityTrace mTrace;
 
-    // The attribution tag set by the service that is bound to this instance
+    /** The attribution tag set by the client that is bound to this instance */
     protected String mAttributionTag;
 
     protected int mDisplayTypes = DISPLAY_TYPE_DEFAULT;
 
-    // The service that's bound to this instance. Whenever this value is non-null, this
-    // object is registered as a death recipient
-    IBinder mService;
+    /**
+     * Binder of the {@link #mClient}.
+     *
+     * <p>Whenever this value is non-null, it should be registered as a {@link
+     * IBinder.DeathRecipient}
+     */
+    @Nullable IBinder mClientBinder;
 
-    IAccessibilityServiceClient mServiceInterface;
+    /**
+     * The accessibility client this class represents.
+     *
+     * <p>The client is in the application process, i.e., it's a client of system_server. Depending
+     * on the use case, the client can be an {@link AccessibilityService}, a {@code UiAutomation},
+     * etc.
+     */
+    @Nullable IAccessibilityServiceClient mClient;
 
     int mEventTypes;
 
@@ -218,10 +229,10 @@
     int mGenericMotionEventSources;
     int mObservedMotionEventSources;
 
-    // the events pending events to be dispatched to this service
+    /** Pending events to be dispatched to the client */
     final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<>();
 
-    /** Whether this service relies on its {@link AccessibilityCache} being up to date */
+    /** Whether the client relies on its {@link AccessibilityCache} being up to date */
     boolean mUsesAccessibilityCache = false;
 
     // Handler only for dispatching accessibility events since we use event
@@ -230,7 +241,7 @@
 
     final SparseArray<IBinder> mOverlayWindowTokens = new SparseArray();
 
-    // All the embedded accessibility overlays that have been added by this service.
+    /** All the embedded accessibility overlays that have been added by the client. */
     private List<SurfaceControl> mOverlays = new ArrayList<>();
 
     /** The timestamp of requesting to take screenshot in milliseconds */
@@ -274,7 +285,8 @@
 
         /**
          * Called back to notify system that the client has changed
-         * @param serviceInfoChanged True if the service's AccessibilityServiceInfo changed.
+         *
+         * @param serviceInfoChanged True if the client's AccessibilityServiceInfo changed.
          */
         void onClientChangeLocked(boolean serviceInfoChanged);
 
@@ -360,21 +372,22 @@
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mIPlatformCompat = IPlatformCompat.Stub.asInterface(
                 ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
-        mEventDispatchHandler = new Handler(mainHandler.getLooper()) {
-            @Override
-            public void handleMessage(Message message) {
-                final int eventType =  message.what;
-                AccessibilityEvent event = (AccessibilityEvent) message.obj;
-                boolean serviceWantsEvent = message.arg1 != 0;
-                notifyAccessibilityEventInternal(eventType, event, serviceWantsEvent);
-            }
-        };
+        mEventDispatchHandler =
+                new Handler(mainHandler.getLooper()) {
+                    @Override
+                    public void handleMessage(Message message) {
+                        final int eventType = message.what;
+                        AccessibilityEvent event = (AccessibilityEvent) message.obj;
+                        boolean clientWantsEvent = message.arg1 != 0;
+                        notifyAccessibilityEventInternal(eventType, event, clientWantsEvent);
+                    }
+                };
         setDynamicallyConfigurableProperties(accessibilityServiceInfo);
     }
 
     @Override
     public boolean onKeyEvent(KeyEvent keyEvent, int sequenceNumber) {
-        if (!mRequestFilterKeyEvents || (mServiceInterface == null)) {
+        if (!mRequestFilterKeyEvents || (mClient == null)) {
             return false;
         }
         if((mAccessibilityServiceInfo.getCapabilities()
@@ -388,7 +401,7 @@
             if (svcClientTracingEnabled()) {
                 logTraceSvcClient("onKeyEvent", keyEvent + ", " + sequenceNumber);
             }
-            mServiceInterface.onKeyEvent(keyEvent, sequenceNumber);
+            mClient.onKeyEvent(keyEvent, sequenceNumber);
         } catch (RemoteException e) {
             return false;
         }
@@ -470,7 +483,7 @@
     }
 
     public boolean canReceiveEventsLocked() {
-        return (mEventTypes != 0 && mService != null);
+        return (mEventTypes != 0 && mClientBinder != null);
     }
 
     @RequiresNoPermission
@@ -520,7 +533,7 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                // If the XML manifest had data to configure the service its info
+                // If the XML manifest had data to configure the AccessibilityService, its info
                 // should be already set. In such a case update only the dynamically
                 // configurable properties.
                 boolean oldRequestIme = mRequestImeApis;
@@ -1733,40 +1746,40 @@
         try {
             // Clear the proxy in the other process so this
             // IAccessibilityServiceConnection can be garbage collected.
-            if (mServiceInterface != null) {
+            if (mClient != null) {
                 if (svcClientTracingEnabled()) {
                     logTraceSvcClient("init", "null, " + mId + ", null");
                 }
-                mServiceInterface.init(null, mId, null);
+                mClient.init(null, mId, null);
             }
         } catch (RemoteException re) {
                 /* ignore */
         }
-        if (mService != null) {
+        if (mClientBinder != null) {
             try {
-                mService.unlinkToDeath(this, 0);
+                mClientBinder.unlinkToDeath(this, 0);
             } catch (NoSuchElementException e) {
                 Slog.e(LOG_TAG, "Failed unregistering death link");
             }
-            mService = null;
+            mClientBinder = null;
         }
 
-        mServiceInterface = null;
+        mClient = null;
         mReceivedAccessibilityButtonCallbackSinceBind = false;
     }
 
     public boolean isConnectedLocked() {
-        return (mService != null);
+        return (mClientBinder != null);
     }
 
     public void notifyAccessibilityEvent(AccessibilityEvent event) {
         synchronized (mLock) {
             final int eventType = event.getEventType();
 
-            final boolean serviceWantsEvent = wantsEventLocked(event);
+            final boolean clientWantsEvent = clientWantsEventLocked(event);
             final boolean requiredForCacheConsistency = mUsesAccessibilityCache
                     && ((AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK & eventType) != 0);
-            if (!serviceWantsEvent && !requiredForCacheConsistency) {
+            if (!clientWantsEvent && !requiredForCacheConsistency) {
                 return;
             }
 
@@ -1774,7 +1787,7 @@
                 return;
             }
             // Make a copy since during dispatch it is possible the event to
-            // be modified to remove its source if the receiving service does
+            // be modified to remove its source if the receiving client does
             // not have permission to access the window content.
             AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);
             Message message;
@@ -1792,22 +1805,20 @@
                 // Send all messages, bypassing mPendingEvents
                 message = mEventDispatchHandler.obtainMessage(eventType, newEvent);
             }
-            message.arg1 = serviceWantsEvent ? 1 : 0;
+            message.arg1 = clientWantsEvent ? 1 : 0;
 
             mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout);
         }
     }
 
     /**
-     * Determines if given event can be dispatched to a service based on the package of the
-     * event source. Specifically, a service is notified if it is interested in events from the
-     * package.
+     * Determines if given event can be dispatched to a client based on the package of the event
+     * source. Specifically, a client is notified if it is interested in events from the package.
      *
      * @param event The event.
-     * @return True if the listener should be notified, false otherwise.
+     * @return True if the client should be notified, false otherwise.
      */
-    private boolean wantsEventLocked(AccessibilityEvent event) {
-
+    private boolean clientWantsEventLocked(AccessibilityEvent event) {
         if (!canReceiveEventsLocked()) {
             return false;
         }
@@ -1838,22 +1849,20 @@
     }
 
     /**
-     * Notifies an accessibility service client for a scheduled event given the event type.
+     * Notifies a client for a scheduled event given the event type.
      *
      * @param eventType The type of the event to dispatch.
      */
     private void notifyAccessibilityEventInternal(
-            int eventType,
-            AccessibilityEvent event,
-            boolean serviceWantsEvent) {
-        IAccessibilityServiceClient listener;
+            int eventType, AccessibilityEvent event, boolean clientWantsEvent) {
+        IAccessibilityServiceClient client;
 
         synchronized (mLock) {
-            listener = mServiceInterface;
+            client = mClient;
 
-            // If the service died/was disabled while the message for dispatching
-            // the accessibility event was propagating the listener may be null.
-            if (listener == null) {
+            // If the client (in the application process) died/was disabled while the message for
+            // dispatching the accessibility event was propagating, "client" may be null.
+            if (client == null) {
                 return;
             }
 
@@ -1868,7 +1877,7 @@
                 //   1) A binder thread calls notifyAccessibilityServiceDelayedLocked
                 //      which posts a message for dispatching an event and stores the event
                 //      in mPendingEvents.
-                //   2) The message is pulled from the queue by the handler on the service
+                //   2) The message is pulled from the queue by the handler on the client
                 //      thread and this method is just about to acquire the lock.
                 //   3) Another binder thread acquires the lock in notifyAccessibilityEvent
                 //   4) notifyAccessibilityEvent recycles the event that this method was about
@@ -1876,7 +1885,7 @@
                 //   5) This method grabs the new event, processes it, and removes it from
                 //      mPendingEvents
                 //   6) The second message dispatched in (4) arrives, but the event has been
-                //      remvoved in (5).
+                //      removed in (5).
                 event = mPendingEvents.get(eventType);
                 if (event == null) {
                     return;
@@ -1893,14 +1902,14 @@
 
         try {
             if (svcClientTracingEnabled()) {
-                logTraceSvcClient("onAccessibilityEvent", event + ";" + serviceWantsEvent);
+                logTraceSvcClient("onAccessibilityEvent", event + ";" + clientWantsEvent);
             }
-            listener.onAccessibilityEvent(event, serviceWantsEvent);
+            client.onAccessibilityEvent(event, clientWantsEvent);
             if (DEBUG) {
-                Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
+                Slog.i(LOG_TAG, "Event " + event + " sent to " + client);
             }
         } catch (RemoteException re) {
-            Slog.e(LOG_TAG, "Error during sending " + event + " to " + listener, re);
+            Slog.e(LOG_TAG, "Error during sending " + event + " to " + client, re);
         } finally {
             event.recycle();
         }
@@ -1978,122 +1987,126 @@
         return (mGenericMotionEventSources & eventSourceWithoutClass) != 0;
     }
 
-
     /**
-     * Called by the invocation handler to notify the service that the
-     * state of magnification has changed.
+     * Called by the invocation handler to notify the client that the state of magnification has
+     * changed.
      */
-    private void notifyMagnificationChangedInternal(int displayId, @NonNull Region region,
-            @NonNull MagnificationConfig config) {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
+    private void notifyMagnificationChangedInternal(
+            int displayId, @NonNull Region region, @NonNull MagnificationConfig config) {
+        final IAccessibilityServiceClient client = getClientSafely();
+        if (client != null) {
             try {
                 if (svcClientTracingEnabled()) {
                     logTraceSvcClient("onMagnificationChanged", displayId + ", " + region + ", "
                             + config.toString());
                 }
-                listener.onMagnificationChanged(displayId, region, config);
+                client.onMagnificationChanged(displayId, region, config);
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error sending magnification changes to " + mService, re);
+                Slog.e(LOG_TAG, "Error sending magnification changes to " + mClientBinder, re);
             }
         }
     }
 
     /**
-     * Called by the invocation handler to notify the service that the state of the soft
-     * keyboard show mode has changed.
+     * Called by the invocation handler to notify the client that the state of the soft keyboard
+     * show mode has changed.
      */
     private void notifySoftKeyboardShowModeChangedInternal(int showState) {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
+        final IAccessibilityServiceClient client = getClientSafely();
+        if (client != null) {
             try {
                 if (svcClientTracingEnabled()) {
                     logTraceSvcClient("onSoftKeyboardShowModeChanged", String.valueOf(showState));
                 }
-                listener.onSoftKeyboardShowModeChanged(showState);
+                client.onSoftKeyboardShowModeChanged(showState);
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error sending soft keyboard show mode changes to " + mService,
+                Slog.e(
+                        LOG_TAG,
+                        "Error sending soft keyboard show mode changes to " + mClientBinder,
                         re);
             }
         }
     }
 
     private void notifyAccessibilityButtonClickedInternal(int displayId) {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
+        final IAccessibilityServiceClient client = getClientSafely();
+        if (client != null) {
             try {
                 if (svcClientTracingEnabled()) {
                     logTraceSvcClient("onAccessibilityButtonClicked", String.valueOf(displayId));
                 }
-                listener.onAccessibilityButtonClicked(displayId);
+                client.onAccessibilityButtonClicked(displayId);
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error sending accessibility button click to " + mService, re);
+                Slog.e(LOG_TAG, "Error sending accessibility button click to " + mClientBinder, re);
             }
         }
     }
 
     private void notifyAccessibilityButtonAvailabilityChangedInternal(boolean available) {
-        // Only notify the service if it's not been notified or the state has changed
+        // Only notify the client if it's not been notified or the state has changed
         if (mReceivedAccessibilityButtonCallbackSinceBind
                 && (mLastAccessibilityButtonCallbackState == available)) {
             return;
         }
         mReceivedAccessibilityButtonCallbackSinceBind = true;
         mLastAccessibilityButtonCallbackState = available;
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
+        final IAccessibilityServiceClient client = getClientSafely();
+        if (client != null) {
             try {
                 if (svcClientTracingEnabled()) {
                     logTraceSvcClient("onAccessibilityButtonAvailabilityChanged",
                             String.valueOf(available));
                 }
-                listener.onAccessibilityButtonAvailabilityChanged(available);
+                client.onAccessibilityButtonAvailabilityChanged(available);
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG,
-                        "Error sending accessibility button availability change to " + mService,
+                Slog.e(
+                        LOG_TAG,
+                        "Error sending accessibility button availability change to "
+                                + mClientBinder,
                         re);
             }
         }
     }
 
     private void notifyGestureInternal(AccessibilityGestureEvent gestureInfo) {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
+        final IAccessibilityServiceClient client = getClientSafely();
+        if (client != null) {
             try {
                 if (svcClientTracingEnabled()) {
                     logTraceSvcClient("onGesture", gestureInfo.toString());
                 }
-                listener.onGesture(gestureInfo);
+                client.onGesture(gestureInfo);
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error during sending gesture " + gestureInfo
-                        + " to " + mService, re);
-            }
-        }
-    }
-
-    private void notifySystemActionsChangedInternal() {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
-            try {
-                if (svcClientTracingEnabled()) {
-                    logTraceSvcClient("onSystemActionsChanged", "");
-                }
-                listener.onSystemActionsChanged();
-            } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error sending system actions change to " + mService,
+                Slog.e(
+                        LOG_TAG,
+                        "Error during sending gesture " + gestureInfo + " to " + mClientBinder,
                         re);
             }
         }
     }
 
+    private void notifySystemActionsChangedInternal() {
+        final IAccessibilityServiceClient client = getClientSafely();
+        if (client != null) {
+            try {
+                if (svcClientTracingEnabled()) {
+                    logTraceSvcClient("onSystemActionsChanged", "");
+                }
+                client.onSystemActionsChanged();
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG, "Error sending system actions change to " + mClientBinder, re);
+            }
+        }
+    }
+
     private void notifyClearAccessibilityCacheInternal() {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
+        final IAccessibilityServiceClient client = getClientSafely();
+        if (client != null) {
             try {
                 if (svcClientTracingEnabled()) {
                     logTraceSvcClient("clearAccessibilityCache", "");
                 }
-                listener.clearAccessibilityCache();
+                client.clearAccessibilityCache();
             } catch (RemoteException re) {
                 Slog.e(LOG_TAG, "Error during requesting accessibility info cache"
                         + " to be cleared.", re);
@@ -2106,70 +2119,66 @@
 
     private void setImeSessionEnabledInternal(IAccessibilityInputMethodSession session,
             boolean enabled) {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null && session != null) {
+        final IAccessibilityServiceClient client = getClientSafely();
+        if (client != null && session != null) {
             try {
                 if (svcClientTracingEnabled()) {
                     logTraceSvcClient("createImeSession", "");
                 }
-                listener.setImeSessionEnabled(session, enabled);
+                client.setImeSessionEnabled(session, enabled);
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG,
-                        "Error requesting IME session from " + mService, re);
+                Slog.e(LOG_TAG, "Error requesting IME session from " + mClientBinder, re);
             }
         }
     }
 
     private void bindInputInternal() {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
+        final IAccessibilityServiceClient client = getClientSafely();
+        if (client != null) {
             try {
                 if (svcClientTracingEnabled()) {
                     logTraceSvcClient("bindInput", "");
                 }
-                listener.bindInput();
+                client.bindInput();
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG,
-                        "Error binding input to " + mService, re);
+                Slog.e(LOG_TAG, "Error binding input to " + mClientBinder, re);
             }
         }
     }
 
     private void unbindInputInternal() {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
+        final IAccessibilityServiceClient client = getClientSafely();
+        if (client != null) {
             try {
                 if (svcClientTracingEnabled()) {
                     logTraceSvcClient("unbindInput", "");
                 }
-                listener.unbindInput();
+                client.unbindInput();
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG,
-                        "Error unbinding input to " + mService, re);
+                Slog.e(LOG_TAG, "Error unbinding input to " + mClientBinder, re);
             }
         }
     }
 
     private void startInputInternal(IRemoteAccessibilityInputConnection connection,
             EditorInfo editorInfo, boolean restarting) {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
+        final IAccessibilityServiceClient client = getClientSafely();
+        if (client != null) {
             try {
                 if (svcClientTracingEnabled()) {
                     logTraceSvcClient("startInput", "editorInfo=" + editorInfo
                             + " restarting=" + restarting);
                 }
-                listener.startInput(connection, editorInfo, restarting);
+                client.startInput(connection, editorInfo, restarting);
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG,
-                        "Error starting input to " + mService, re);
+                Slog.e(LOG_TAG, "Error starting input to " + mClientBinder, re);
             }
         }
     }
 
-    protected IAccessibilityServiceClient getServiceInterfaceSafely() {
+    protected IAccessibilityServiceClient getClientSafely() {
         synchronized (mLock) {
-            return mServiceInterface;
+            return mClient;
         }
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 7580b69..d595d02 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1435,8 +1435,8 @@
                 interfacesToInterrupt = new ArrayList<>(services.size());
                 for (int i = 0; i < services.size(); i++) {
                     AccessibilityServiceConnection service = services.get(i);
-                    IBinder a11yServiceBinder = service.mService;
-                    IAccessibilityServiceClient a11yServiceInterface = service.mServiceInterface;
+                    IBinder a11yServiceBinder = service.mClientBinder;
+                    IAccessibilityServiceClient a11yServiceInterface = service.mClient;
                     if ((a11yServiceBinder != null) && (a11yServiceInterface != null)) {
                         interfacesToInterrupt.add(a11yServiceInterface);
                     }
@@ -4962,9 +4962,14 @@
                 if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
                         && android.security.Flags.extendEcmToAllSettings()) {
                     try {
-                        return !mContext.getSystemService(EnhancedConfirmationManager.class)
-                                .isRestricted(packageName,
-                                        AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
+                        final EnhancedConfirmationManager userContextEcm =
+                                mContext.createContextAsUser(UserHandle.of(userId), /* flags = */ 0)
+                                        .getSystemService(EnhancedConfirmationManager.class);
+                        if (userContextEcm != null) {
+                            return !userContextEcm.isRestricted(packageName,
+                                    AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
+                        }
+                        return false;
                     } catch (PackageManager.NameNotFoundException e) {
                         Log.e(LOG_TAG, "Exception when retrieving package:" + packageName, e);
                         return false;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 786d167..15999d1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -166,8 +166,9 @@
             if (userState.getBindInstantServiceAllowedLocked()) {
                 flags |= Context.BIND_ALLOW_INSTANT;
             }
-            if (mService == null && mContext.bindServiceAsUser(
-                    mIntent, this, flags, new UserHandle(userState.mUserId))) {
+            if (mClientBinder == null
+                    && mContext.bindServiceAsUser(
+                            mIntent, this, flags, new UserHandle(userState.mUserId))) {
                 userState.getBindingServicesLocked().add(mComponentName);
             }
         } finally {
@@ -227,20 +228,20 @@
             addWindowTokensForAllDisplays();
         }
         synchronized (mLock) {
-            if (mService != service) {
-                if (mService != null) {
-                    mService.unlinkToDeath(this, 0);
+            if (mClientBinder != service) {
+                if (mClientBinder != null) {
+                    mClientBinder.unlinkToDeath(this, 0);
                 }
-                mService = service;
+                mClientBinder = service;
                 try {
-                    mService.linkToDeath(this, 0);
+                    mClientBinder.linkToDeath(this, 0);
                 } catch (RemoteException re) {
                     Slog.e(LOG_TAG, "Failed registering death link");
                     binderDied();
                     return;
                 }
             }
-            mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service);
+            mClient = IAccessibilityServiceClient.Stub.asInterface(service);
             if (userState == null) return;
             userState.addServiceLocked(this);
             mSystemSupport.onClientChangeLocked(false);
@@ -261,7 +262,7 @@
     }
 
     private void initializeService() {
-        IAccessibilityServiceClient serviceInterface = null;
+        IAccessibilityServiceClient client = null;
         synchronized (mLock) {
             AccessibilityUserState userState = mUserStateWeakReference.get();
             if (userState == null) return;
@@ -272,18 +273,17 @@
                 bindingServices.remove(mComponentName);
                 crashedServices.remove(mComponentName);
                 mAccessibilityServiceInfo.crashed = false;
-                serviceInterface = mServiceInterface;
+                client = mClient;
             }
             // There's a chance that service is removed from enabled_accessibility_services setting
             // key, but skip unbinding because of it's in binding state. Unbinds it if it's
             // not in enabled service list.
-            if (serviceInterface != null
-                    && !userState.getEnabledServicesLocked().contains(mComponentName)) {
+            if (client != null && !userState.getEnabledServicesLocked().contains(mComponentName)) {
                 mSystemSupport.onClientChangeLocked(false);
                 return;
             }
         }
-        if (serviceInterface == null) {
+        if (client == null) {
             binderDied();
             return;
         }
@@ -292,10 +292,9 @@
                 logTraceSvcClient("init",
                         this + "," + mId + "," + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
             }
-            serviceInterface.init(this, mId, mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
+            client.init(this, mId, mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
         } catch (RemoteException re) {
-            Slog.w(LOG_TAG, "Error while setting connection for service: "
-                    + serviceInterface, re);
+            Slog.w(LOG_TAG, "Error while setting connection for service: " + client, re);
             binderDied();
         }
     }
@@ -496,7 +495,7 @@
 
     @Override
     public boolean isCapturingFingerprintGestures() {
-        return (mServiceInterface != null)
+        return (mClient != null)
                 && mSecurityPolicy.canCaptureFingerprintGestures(this)
                 && mCaptureFingerprintGestures;
     }
@@ -506,17 +505,17 @@
         if (!isCapturingFingerprintGestures()) {
             return;
         }
-        IAccessibilityServiceClient serviceInterface;
+        IAccessibilityServiceClient client;
         synchronized (mLock) {
-            serviceInterface = mServiceInterface;
+            client = mClient;
         }
-        if (serviceInterface != null) {
+        if (client != null) {
             try {
                 if (svcClientTracingEnabled()) {
                     logTraceSvcClient(
                             "onFingerprintCapturingGesturesChanged", String.valueOf(active));
                 }
-                mServiceInterface.onFingerprintCapturingGesturesChanged(active);
+                mClient.onFingerprintCapturingGesturesChanged(active);
             } catch (RemoteException e) {
             }
         }
@@ -527,16 +526,16 @@
         if (!isCapturingFingerprintGestures()) {
             return;
         }
-        IAccessibilityServiceClient serviceInterface;
+        IAccessibilityServiceClient client;
         synchronized (mLock) {
-            serviceInterface = mServiceInterface;
+            client = mClient;
         }
-        if (serviceInterface != null) {
+        if (client != null) {
             try {
                 if (svcClientTracingEnabled()) {
                     logTraceSvcClient("onFingerprintGesture", String.valueOf(gesture));
                 }
-                mServiceInterface.onFingerprintGesture(gesture);
+                mClient.onFingerprintGesture(gesture);
             } catch (RemoteException e) {
             }
         }
@@ -546,7 +545,7 @@
     @Override
     public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) {
         synchronized (mLock) {
-            if (mServiceInterface != null && mSecurityPolicy.canPerformGestures(this)) {
+            if (mClient != null && mSecurityPolicy.canPerformGestures(this)) {
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     MotionEventInjector motionEventInjector =
@@ -557,16 +556,18 @@
                     if (motionEventInjector != null
                             && mWindowManagerService.isTouchOrFaketouchDevice()) {
                         motionEventInjector.injectEvents(
-                                gestureSteps.getList(), mServiceInterface, sequence, displayId);
+                                gestureSteps.getList(), mClient, sequence, displayId);
                     } else {
                         try {
                             if (svcClientTracingEnabled()) {
                                 logTraceSvcClient("onPerformGestureResult", sequence + ", false");
                             }
-                            mServiceInterface.onPerformGestureResult(sequence, false);
+                            mClient.onPerformGestureResult(sequence, false);
                         } catch (RemoteException re) {
-                            Slog.e(LOG_TAG, "Error sending motion event injection failure to "
-                                    + mServiceInterface, re);
+                            Slog.e(
+                                    LOG_TAG,
+                                    "Error sending motion event injection failure to " + mClient,
+                                    re);
                         }
                     }
                 } finally {
@@ -631,48 +632,47 @@
 
     @Override
     protected void createImeSessionInternal() {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
+        final IAccessibilityServiceClient client = getClientSafely();
+        if (client != null) {
             try {
                 if (svcClientTracingEnabled()) {
                     logTraceSvcClient("createImeSession", "");
                 }
                 AccessibilityInputMethodSessionCallback
                         callback = new AccessibilityInputMethodSessionCallback(mUserId);
-                listener.createImeSession(callback);
+                client.createImeSession(callback);
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG,
-                        "Error requesting IME session from " + mService, re);
+                Slog.e(LOG_TAG, "Error requesting IME session from " + mClientBinder, re);
             }
         }
     }
 
     private void notifyMotionEventInternal(MotionEvent event) {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
+        final IAccessibilityServiceClient client = getClientSafely();
+        if (client != null) {
             try {
                 if (mTrace.isA11yTracingEnabled()) {
                     logTraceSvcClient(".onMotionEvent ",
                             event.toString());
                 }
-                listener.onMotionEvent(event);
+                client.onMotionEvent(event);
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error sending motion event to" + mService, re);
+                Slog.e(LOG_TAG, "Error sending motion event to" + mClientBinder, re);
             }
         }
     }
 
     private void notifyTouchStateInternal(int displayId, int state) {
-        final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
-        if (listener != null) {
+        final IAccessibilityServiceClient client = getClientSafely();
+        if (client != null) {
             try {
                 if (mTrace.isA11yTracingEnabled()) {
                     logTraceSvcClient(".onTouchStateChanged ",
                             TouchInteractionController.stateToString(state));
                 }
-                listener.onTouchStateChanged(displayId, state);
+                client.onTouchStateChanged(displayId, state);
             } catch (RemoteException re) {
-                Slog.e(LOG_TAG, "Error sending motion event to" + mService, re);
+                Slog.e(LOG_TAG, "Error sending motion event to" + mClientBinder, re);
             }
         }
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index 4cb3d24..cd97d83 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -109,14 +109,11 @@
         return mDeviceId;
     }
 
-    /**
-     * Called when the proxy is registered.
-     */
-    void initializeServiceInterface(IAccessibilityServiceClient serviceInterface)
-            throws RemoteException {
-        mServiceInterface = serviceInterface;
-        mService = serviceInterface.asBinder();
-        mServiceInterface.init(this, mId, this.mOverlayWindowTokens.get(mDisplayId));
+    /** Called when the proxy is registered. */
+    void initializeClient(IAccessibilityServiceClient client) throws RemoteException {
+        mClient = client;
+        mClientBinder = client.asBinder();
+        mClient.init(this, mId, this.mOverlayWindowTokens.get(mDisplayId));
     }
 
     /**
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index b4deeb0..da11a76 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -214,7 +214,7 @@
                 mA11yInputFilter.disableFeaturesForDisplayIfInstalled(displayId);
             }
         });
-        connection.initializeServiceInterface(client);
+        connection.initializeClient(client);
     }
 
     private void registerVirtualDeviceListener() {
@@ -561,8 +561,8 @@
             final ProxyAccessibilityServiceConnection proxy =
                     mProxyA11yServiceConnections.valueAt(i);
             if (proxy != null && proxy.getDeviceId() == deviceId) {
-                final IBinder proxyBinder = proxy.mService;
-                final IAccessibilityServiceClient proxyInterface = proxy.mServiceInterface;
+                final IBinder proxyBinder = proxy.mClientBinder;
+                final IAccessibilityServiceClient proxyInterface = proxy.mClient;
                 if ((proxyBinder != null) && (proxyInterface != null)) {
                     interfaces.add(proxyInterface);
                 }
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index f85d786..ed4eeb5 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -107,8 +107,7 @@
                 Binder.getCallingUserHandle().getIdentifier());
         if (mUiAutomationService != null) {
             throw new IllegalStateException(
-                    "UiAutomationService " + mUiAutomationService.mServiceInterface
-                            + "already registered!");
+                    "UiAutomationService " + mUiAutomationService.mClient + "already registered!");
         }
 
         try {
@@ -130,10 +129,9 @@
                 mainHandler, mLock, securityPolicy, systemSupport, trace, windowManagerInternal,
                 systemActionPerformer, awm);
         mUiAutomationServiceOwner = owner;
-        mUiAutomationService.mServiceInterface = serviceClient;
+        mUiAutomationService.mClient = serviceClient;
         try {
-            mUiAutomationService.mServiceInterface.asBinder().linkToDeath(mUiAutomationService,
-                    0);
+            mUiAutomationService.mClient.asBinder().linkToDeath(mUiAutomationService, 0);
         } catch (RemoteException re) {
             Slog.e(LOG_TAG, "Failed registering death link: " + re);
             destroyUiAutomationService();
@@ -149,10 +147,10 @@
         synchronized (mLock) {
             if (useAccessibility()
                     && ((mUiAutomationService == null)
-                    || (serviceClient == null)
-                    || (mUiAutomationService.mServiceInterface == null)
-                    || (serviceClient.asBinder()
-                    != mUiAutomationService.mServiceInterface.asBinder()))) {
+                            || (serviceClient == null)
+                            || (mUiAutomationService.mClient == null)
+                            || (serviceClient.asBinder()
+                                    != mUiAutomationService.mClient.asBinder()))) {
                 throw new IllegalStateException("UiAutomationService " + serviceClient
                         + " not registered!");
             }
@@ -230,8 +228,7 @@
     private void destroyUiAutomationService() {
         synchronized (mLock) {
             if (mUiAutomationService != null) {
-                mUiAutomationService.mServiceInterface.asBinder().unlinkToDeath(
-                        mUiAutomationService, 0);
+                mUiAutomationService.mClient.asBinder().unlinkToDeath(mUiAutomationService, 0);
                 mUiAutomationService.onRemoved();
                 mUiAutomationService.resetLocked();
                 mUiAutomationService = null;
@@ -271,40 +268,48 @@
 
         void connectServiceUnknownThread() {
             // This needs to be done on the main thread
-            mMainHandler.post(() -> {
-                try {
-                    final IAccessibilityServiceClient serviceInterface;
-                    final UiAutomationService uiAutomationService;
-                    synchronized (mLock) {
-                        serviceInterface = mServiceInterface;
-                        uiAutomationService = mUiAutomationService;
-                        if (serviceInterface == null) {
-                            mService = null;
-                        } else {
-                            mService = mServiceInterface.asBinder();
-                            mService.linkToDeath(this, 0);
+            mMainHandler.post(
+                    () -> {
+                        try {
+                            final IAccessibilityServiceClient client;
+                            final UiAutomationService uiAutomationService;
+                            synchronized (mLock) {
+                                client = mClient;
+                                uiAutomationService = mUiAutomationService;
+                                if (client == null) {
+                                    mClientBinder = null;
+                                } else {
+                                    mClientBinder = mClient.asBinder();
+                                    mClientBinder.linkToDeath(this, 0);
+                                }
+                            }
+                            // If the client is null, the UiAutomation has been shut down on
+                            // another thread.
+                            if (client != null && uiAutomationService != null) {
+                                uiAutomationService.addWindowTokensForAllDisplays();
+                                if (mTrace.isA11yTracingEnabledForTypes(
+                                        AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
+                                    mTrace.logTrace(
+                                            "UiAutomationService.connectServiceUnknownThread",
+                                            AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT,
+                                            "serviceConnection="
+                                                    + this
+                                                    + ";connectionId="
+                                                    + mId
+                                                    + "windowToken="
+                                                    + mOverlayWindowTokens.get(
+                                                            Display.DEFAULT_DISPLAY));
+                                }
+                                client.init(
+                                        this,
+                                        mId,
+                                        mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
+                            }
+                        } catch (RemoteException re) {
+                            Slog.w(LOG_TAG, "Error initializing connection", re);
+                            destroyUiAutomationService();
                         }
-                    }
-                    // If the serviceInterface is null, the UiAutomation has been shut down on
-                    // another thread.
-                    if (serviceInterface != null && uiAutomationService != null) {
-                        uiAutomationService.addWindowTokensForAllDisplays();
-                        if (mTrace.isA11yTracingEnabledForTypes(
-                                AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
-                            mTrace.logTrace("UiAutomationService.connectServiceUnknownThread",
-                                    AccessibilityTrace.FLAGS_ACCESSIBILITY_SERVICE_CLIENT,
-                                    "serviceConnection=" + this + ";connectionId=" + mId
-                                    + "windowToken="
-                                    + mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
-                        }
-                        serviceInterface.init(this, mId,
-                                mOverlayWindowTokens.get(Display.DEFAULT_DISPLAY));
-                    }
-                } catch (RemoteException re) {
-                    Slog.w(LOG_TAG, "Error initializing connection", re);
-                    destroyUiAutomationService();
-                }
-            });
+                    });
         }
 
         @Override
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index b2c679f..f6ac706 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -1444,7 +1444,7 @@
      * in {@link #allocateAppWidgetId}.
      *
      * @param callingPackage The package that calls this method.
-     * @param appWidgetId The id of theapp widget to bind.
+     * @param appWidgetId The id of the widget to bind.
      * @param providerProfileId The user/profile id of the provider.
      * @param providerComponent The {@link ComponentName} that provides the widget.
      * @param options The options to pass to the provider.
@@ -1738,6 +1738,14 @@
         return false;
     }
 
+    /**
+     * Called by a {@link AppWidgetHost} to remove all records (i.e. {@link Host}
+     * and all {@link Widget} associated with the host) from a specified host.
+     *
+     * @param callingPackage The package that calls this method.
+     * @param hostId id of the {@link Host}.
+     * @see AppWidgetHost#deleteHost()
+     */
     @Override
     public void deleteHost(String callingPackage, int hostId) {
         final int userId = UserHandle.getCallingUserId();
@@ -1771,6 +1779,15 @@
         }
     }
 
+    /**
+     * Called by a host process to remove all records (i.e. {@link Host}
+     * and all {@link Widget} associated with the host) from all hosts associated
+     * with the calling process.
+     *
+     * Typically used in clean up after test execution.
+     *
+     * @see AppWidgetHost#deleteAllHosts()
+     */
     @Override
     public void deleteAllHosts() {
         final int userId = UserHandle.getCallingUserId();
@@ -1805,6 +1822,18 @@
         }
     }
 
+    /**
+     * Returns the {@link AppWidgetProviderInfo} for the specified AppWidget.
+     *
+     * Typically used by launcher during the restore of an AppWidget, the binding
+     * of new AppWidget, and during grid size migration.
+     *
+     * @param callingPackage The package that calls this method.
+     * @param appWidgetId   Id of the widget.
+     * @return The {@link AppWidgetProviderInfo} for the specified widget.
+     *
+     * @see AppWidgetManager#getAppWidgetInfo(int)
+     */
     @Override
     public AppWidgetProviderInfo getAppWidgetInfo(String callingPackage, int appWidgetId) {
         final int userId = UserHandle.getCallingUserId();
@@ -1859,6 +1888,17 @@
         }
     }
 
+    /**
+     * Returns the most recent {@link RemoteViews} of the specified AppWidget.
+     * Typically serves as a cache of the content of the AppWidget.
+     *
+     * @param callingPackage The package that calls this method.
+     * @param appWidgetId   Id of the widget.
+     * @return The {@link RemoteViews} of the specified widget.
+     *
+     * @see AppWidgetHost#updateAppWidgetDeferred(String, int)
+     * @see AppWidgetHost#setListener(int, AppWidgetHostListener)
+     */
     @Override
     public RemoteViews getAppWidgetViews(String callingPackage, int appWidgetId) {
         final int userId = UserHandle.getCallingUserId();
@@ -1886,6 +1926,29 @@
         }
     }
 
+    /**
+     * Update the extras for a given widget instance.
+     * <p>
+     * The extras can be used to embed additional information about this widget to be accessed
+     * by the associated widget's AppWidgetProvider.
+     *
+     * <p>
+     * The new options are merged into existing options using {@link Bundle#putAll} semantics.
+     *
+     * <p>
+     * Typically called by a {@link AppWidgetHost} (e.g. Launcher) to notify
+     * {@link AppWidgetProvider} regarding contextual changes (e.g. sizes) when rendering the
+     * widget.
+     * Calling this method would trigger onAppWidgetOptionsChanged() callback on the provider's
+     * side.
+     *
+     * @param callingPackage The package that calls this method.
+     * @param appWidgetId Id of the widget.
+     * @param options New options associate with this widget.
+     *
+     * @see AppWidgetManager#getAppWidgetOptions(int, Bundle)
+     * @see AppWidgetProvider#onAppWidgetOptionsChanged(Context, AppWidgetManager, int, Bundle)
+     */
     @Override
     public void updateAppWidgetOptions(String callingPackage, int appWidgetId, Bundle options) {
         final int userId = UserHandle.getCallingUserId();
@@ -1919,6 +1982,21 @@
         }
     }
 
+    /**
+     * Get the extras associated with a given widget instance.
+     * <p>
+     * The extras can be used to embed additional information about this widget to be accessed
+     * by the associated widget's AppWidgetProvider.
+     *
+     * Typically called by a host process (e.g. Launcher) to determine if they need to update the
+     * options of the widget.
+     *
+     * @see #updateAppWidgetOptions(String, int, Bundle)
+     *
+     * @param callingPackage The package that calls this method.
+     * @param appWidgetId Id of the widget.
+     * @return The options associated with the specified widget instance.
+     */
     @Override
     public Bundle getAppWidgetOptions(String callingPackage, int appWidgetId) {
         final int userId = UserHandle.getCallingUserId();
@@ -1946,6 +2024,28 @@
         }
     }
 
+    /**
+     * Updates the content of the widgets (as specified by appWidgetIds) using the provided
+     * {@link RemoteViews}.
+     *
+     * Typically called by the provider's process. Either in response to the invocation of
+     * {@link AppWidgetProvider#onUpdate} or upon receiving the
+     * {@link AppWidgetManager#ACTION_APPWIDGET_UPDATE} broadcast.
+     *
+     * <p>
+     * Note that the RemoteViews parameter will be cached by the AppWidgetService, and hence should
+     * contain a complete representation of the widget. For performing partial widget updates, see
+     * {@link #partiallyUpdateAppWidgetIds(String, int[], RemoteViews)}.
+     *
+     * @param callingPackage The package that calls this method.
+     * @param appWidgetIds Ids of the widgets to be updated.
+     * @param views The RemoteViews object containing the update.
+     *
+     * @see AppWidgetProvider#onUpdate(Context, AppWidgetManager, int[])
+     * @see AppWidgetManager#ACTION_APPWIDGET_UPDATE
+     * @see AppWidgetManager#updateAppWidget(int, RemoteViews)
+     * @see AppWidgetManager#updateAppWidget(int[], RemoteViews)
+     */
     @Override
     public void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
             RemoteViews views) {
@@ -1956,6 +2056,27 @@
         updateAppWidgetIds(callingPackage, appWidgetIds, views, false);
     }
 
+    /**
+     * Perform an incremental update or command on the widget(s) specified by appWidgetIds.
+     * <p>
+     * This update  differs from {@link #updateAppWidgetIds(int[], RemoteViews)} in that the
+     * RemoteViews object which is passed is understood to be an incomplete representation of the
+     * widget, and hence does not replace the cached representation of the widget. As of API
+     * level 17, the new properties set within the views objects will be appended to the cached
+     * representation of the widget, and hence will persist.
+     *
+     * <p>
+     * This method will be ignored if a widget has not received a full update via
+     * {@link #updateAppWidget(int[], RemoteViews)}.
+     *
+     * @param callingPackage   The package that calls this method.
+     * @param appWidgetIds     Ids of the widgets to be updated.
+     * @param views            The RemoteViews object containing the incremental update / command.
+     *
+     * @see AppWidgetManager#partiallyUpdateAppWidget(int[], RemoteViews)
+     * @see RemoteViews#setDisplayedChild(int, int)
+     * @see RemoteViews#setScrollPosition(int, int)
+     */
     @Override
     public void partiallyUpdateAppWidgetIds(String callingPackage, int[] appWidgetIds,
             RemoteViews views) {
@@ -1966,6 +2087,24 @@
         updateAppWidgetIds(callingPackage, appWidgetIds, views, true);
     }
 
+    /**
+     * Callback function which marks specified providers as extended from AppWidgetProvider.
+     *
+     * This information is used to determine if the system can combine
+     * {@link AppWidgetManager#ACTION_APPWIDGET_ENABLED} and
+     * {@link AppWidgetManager#ACTION_APPWIDGET_UPDATE} into a single broadcast.
+     *
+     * Note: The system can only combine the two broadcasts if the provider is extended from
+     * AppWidgetProvider. When they do, they are expected to override the
+     * {@link AppWidgetProvider#onUpdate} callback function to provide updates, as opposed to
+     * listening for {@link AppWidgetManager#ACTION_APPWIDGET_UPDATE} broadcasts directly.
+     *
+     * @see AppWidgetManager#ACTION_APPWIDGET_ENABLED
+     * @see AppWidgetManager#ACTION_APPWIDGET_UPDATE
+     * @see AppWidgetManager#ACTION_APPWIDGET_ENABLE_AND_UPDATE
+     * @see AppWidgetProvider#onReceive(Context, Intent)
+     * @see #sendEnableAndUpdateIntentLocked
+     */
     @Override
     public void notifyProviderInheritance(@Nullable final ComponentName[] componentNames) {
         final int userId = UserHandle.getCallingUserId();
@@ -2000,6 +2139,15 @@
         }
     }
 
+    /**
+     * Notifies the specified collection view in all the specified AppWidget instances
+     * to invalidate their data.
+     *
+     * This method is effectively deprecated since
+     * {@link RemoteViews#setRemoteAdapter(int, Intent)} has been deprecated.
+     *
+     * @see AppWidgetManager#notifyAppWidgetViewDataChanged(int[], int)
+     */
     @Override
     public void notifyAppWidgetViewDataChanged(String callingPackage, int[] appWidgetIds,
             int viewId) {
@@ -2035,6 +2183,18 @@
         }
     }
 
+    /**
+     * Updates the content of all widgets associated with given provider (as specified by
+     * componentName) using the provided {@link RemoteViews}.
+     *
+     * Typically called by the provider's process when there's an update that needs to be supplied
+     * to all instances of the widgets.
+     *
+     * @param componentName The component name of the provider.
+     * @param views The RemoteViews object containing the update.
+     *
+     * @see AppWidgetManager#updateAppWidget(ComponentName, RemoteViews)
+     */
     @Override
     public void updateAppWidgetProvider(ComponentName componentName, RemoteViews views) {
         final int userId = UserHandle.getCallingUserId();
@@ -2068,6 +2228,27 @@
         }
     }
 
+    /**
+     * Updates the info for the supplied AppWidget provider. Apps can use this to change the default
+     * behavior of the widget based on the state of the app (e.g., if the user is logged in
+     * or not). Calling this API completely replaces the previous definition.
+     *
+     * <p>
+     * The manifest entry of the provider should contain an additional meta-data tag similar to
+     * {@link AppWidgetManager#META_DATA_APPWIDGET_PROVIDER} which should point to any alternative
+     * definitions for the provider.
+     *
+     * <p>
+     * This is persisted across device reboots and app updates. If this meta-data key is not
+     * present in the manifest entry, the info reverts to default.
+     *
+     * @param provider {@link ComponentName} for the {@link
+     *    android.content.BroadcastReceiver BroadcastReceiver} provider for your AppWidget.
+     * @param metaDataKey key for the meta-data tag pointing to the new provider info. Use null
+     *    to reset any previously set info.
+     *
+     * @see AppWidgetManager#updateAppWidgetProviderInfo(ComponentName, String)
+     */
     @Override
     public void updateAppWidgetProviderInfo(ComponentName componentName, String metadataKey) {
         final int userId = UserHandle.getCallingUserId();
@@ -2119,6 +2300,11 @@
         }
     }
 
+    /**
+     * Returns true if the default launcher app on the device (the one that currently
+     * holds the android.app.role.HOME role) can support pinning widgets
+     * (typically means adding widgets into home screen).
+     */
     @Override
     public boolean isRequestPinAppWidgetSupported() {
         synchronized (mLock) {
@@ -2133,6 +2319,44 @@
                         LauncherApps.PinItemRequest.REQUEST_TYPE_APPWIDGET);
     }
 
+    /**
+     * Request to pin an app widget on the current launcher. It's up to the launcher to accept this
+     * request (optionally showing a user confirmation). If the request is accepted, the caller will
+     * get a confirmation with extra {@link #EXTRA_APPWIDGET_ID}.
+     *
+     * <p>When a request is denied by the user, the caller app will not get any response.
+     *
+     * <p>Only apps with a foreground activity or a foreground service can call it.  Otherwise
+     * it'll throw {@link IllegalStateException}.
+     *
+     * <p>It's up to the launcher how to handle previous pending requests when the same package
+     * calls this API multiple times in a row.  It may ignore the previous requests,
+     * for example.
+     *
+     * <p>Launcher will not show the configuration activity associated with the provider in this
+     * case. The app could either show the configuration activity as a response to the callback,
+     * or show if before calling the API (various configurations can be encapsulated in
+     * {@code successCallback} to avoid persisting them before the widgetId is known).
+     *
+     * @param provider The {@link ComponentName} for the {@link
+     *    android.content.BroadcastReceiver BroadcastReceiver} provider for your AppWidget.
+     * @param extras If not null, this is passed to the launcher app. For eg {@link
+     *    #EXTRA_APPWIDGET_PREVIEW} can be used for a custom preview.
+     * @param successCallback If not null, this intent will be sent when the widget is created.
+     *
+     * @return {@code TRUE} if the launcher supports this feature. Note the API will return without
+     *    waiting for the user to respond, so getting {@code TRUE} from this API does *not* mean
+     *    the shortcut is pinned. {@code FALSE} if the launcher doesn't support this feature or if
+     *    calling app belongs to a user-profile with items restricted on home screen.
+     *
+     * @see android.content.pm.ShortcutManager#isRequestPinShortcutSupported()
+     * @see android.content.pm.ShortcutManager#requestPinShortcut(ShortcutInfo, IntentSender)
+     * @see AppWidgetManager#isRequestPinAppWidgetSupported()
+     * @see AppWidgetManager#requestPinAppWidget(ComponentName, Bundle, PendingIntent)
+     *
+     * @throws IllegalStateException The caller doesn't have a foreground activity or a foreground
+     * service or when the user is locked.
+     */
     @Override
     public boolean requestPinAppWidget(String callingPackage, ComponentName componentName,
             Bundle extras, IntentSender resultSender) {
@@ -2184,6 +2408,24 @@
                 callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
     }
 
+    /**
+     * Gets the AppWidget providers for the given user profile. User profile can only
+     * be the current user or a profile of the current user. For example, the current
+     * user may have a corporate profile. In this case the parent user profile has a
+     * child profile, the corporate one.
+     *
+     * @param categoryFilter Will only return providers which register as any of the specified
+     *        specified categories. See {@link AppWidgetProviderInfo#widgetCategory}.
+     * @param profile A profile of the current user which to be queried. The user
+     *        is itself also a profile. If null, the providers only for the current user
+     *        are returned.
+     * @param packageName If specified, will only return providers from the given package.
+     * @return The installed providers.
+     *
+     * @see android.os.Process#myUserHandle()
+     * @see android.os.UserManager#getUserProfiles()
+     * @see AppWidgetManager#getInstalledProvidersForProfile(int, UserHandle, String)
+     */
     @Override
     public ParceledListSlice<AppWidgetProviderInfo> getInstalledProvidersForProfile(int categoryFilter,
             int profileId, String packageName) {
@@ -2244,6 +2486,26 @@
         }
     }
 
+    /**
+     * Updates the content of the widgets (as specified by appWidgetIds) using the provided
+     * {@link RemoteViews}.
+     *
+     * If performing a partial update, the given RemoteViews object is merged into existing
+     * RemoteViews object.
+     *
+     * Fails silently if appWidgetIds is null or empty, or cannot found a widget with the given
+     * appWidgetId.
+     *
+     * @param callingPackage The package that calls this method.
+     * @param appWidgetIds Ids of the widgets to be updated.
+     * @param views The RemoteViews object containing the update.
+     * @param partially Whether it was a partial update.
+     *
+     * @see AppWidgetProvider#onUpdate(Context, AppWidgetManager, int[])
+     * @see AppWidgetManager#ACTION_APPWIDGET_UPDATE
+     * @see AppWidgetManager#updateAppWidget(int, RemoteViews)
+     * @see AppWidgetManager#updateAppWidget(int[], RemoteViews)
+     */
     private void updateAppWidgetIds(String callingPackage, int[] appWidgetIds,
             RemoteViews views, boolean partially) {
         final int userId = UserHandle.getCallingUserId();
@@ -2273,12 +2535,29 @@
         }
     }
 
+    /**
+     * Increment the counter of widget ids and return the new id.
+     *
+     * Typically called by {@link #allocateAppWidgetId} when a instance of widget is created,
+     * either as a result of being pinned by launcher or added during a restore.
+     *
+     * Note: A widget id is a monotonically increasing integer that uniquely identifies the widget
+     * instance.
+     *
+     * TODO: Revisit this method and determine whether we need to alter the widget id during
+     *       the restore since widget id mismatch potentially leads to some issues in the past.
+     */
     private int incrementAndGetAppWidgetIdLocked(int userId) {
         final int appWidgetId = peekNextAppWidgetIdLocked(userId) + 1;
         mNextAppWidgetIds.put(userId, appWidgetId);
         return appWidgetId;
     }
 
+    /**
+     * Called by {@link #readProfileStateFromFileLocked} when widgets/providers/hosts are loaded
+     * from disk, which ensures mNextAppWidgetIds is larger than any existing widget id for given
+     * user.
+     */
     private void setMinAppWidgetIdLocked(int userId, int minWidgetId) {
         final int nextAppWidgetId = peekNextAppWidgetIdLocked(userId);
         if (nextAppWidgetId < minWidgetId) {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 88edb12..3499a3a 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1773,8 +1773,7 @@
                         // Create a Session for the target user and pass in the bundle
                         completeCloningAccount(response, result, account, toAccounts, userFrom);
                     } else {
-                        // Bundle format is not defined.
-                        super.onResultSkipSanitization(result);
+                        super.onResult(result);
                     }
                 }
             }.bind();
@@ -1861,8 +1860,7 @@
                     // account to avoid retries?
                     // TODO: what we do with the visibility?
 
-                    // Bundle format is not defined.
-                    super.onResultSkipSanitization(result);
+                    super.onResult(result);
                 }
 
                 @Override
@@ -2108,7 +2106,6 @@
         @Override
         public void onResult(Bundle result) {
             Bundle.setDefusable(result, true);
-            result = sanitizeBundle(result);
             IAccountManagerResponse response = getResponseAndClose();
             if (response != null) {
                 try {
@@ -2462,7 +2459,6 @@
         @Override
         public void onResult(Bundle result) {
             Bundle.setDefusable(result, true);
-            result = sanitizeBundle(result);
             if (result != null && result.containsKey(AccountManager.KEY_BOOLEAN_RESULT)
                     && !result.containsKey(AccountManager.KEY_INTENT)) {
                 final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
@@ -2977,7 +2973,6 @@
                 @Override
                 public void onResult(Bundle result) {
                     Bundle.setDefusable(result, true);
-                    result = sanitizeBundle(result);
                     if (result != null) {
                         String label = result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL);
                         Bundle bundle = new Bundle();
@@ -3155,7 +3150,6 @@
                 @Override
                 public void onResult(Bundle result) {
                     Bundle.setDefusable(result, true);
-                    result = sanitizeBundle(result);
                     if (result != null) {
                         if (result.containsKey(AccountManager.KEY_AUTH_TOKEN_LABEL)) {
                             Intent intent = newGrantCredentialsPermissionIntent(
@@ -3627,12 +3621,6 @@
         @Override
         public void onResult(Bundle result) {
             Bundle.setDefusable(result, true);
-            Bundle sessionBundle = null;
-            if (result != null) {
-                // Session bundle will be removed from result.
-                sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
-            }
-            result = sanitizeBundle(result);
             mNumResults++;
             Intent intent = null;
             if (result != null) {
@@ -3694,6 +3682,7 @@
             // bundle contains data necessary for finishing the session
             // later. The session bundle will be encrypted here and
             // decrypted later when trying to finish the session.
+            Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
             if (sessionBundle != null) {
                 String accountType = sessionBundle.getString(AccountManager.KEY_ACCOUNT_TYPE);
                 if (TextUtils.isEmpty(accountType)
@@ -4081,7 +4070,6 @@
                 @Override
                 public void onResult(Bundle result) {
                     Bundle.setDefusable(result, true);
-                    result = sanitizeBundle(result);
                     IAccountManagerResponse response = getResponseAndClose();
                     if (response == null) {
                         return;
@@ -4395,7 +4383,6 @@
         @Override
         public void onResult(Bundle result) {
             Bundle.setDefusable(result, true);
-            result = sanitizeBundle(result);
             mNumResults++;
             if (result == null) {
                 onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
@@ -4952,68 +4939,6 @@
                 callback, resultReceiver);
     }
 
-
-    // All keys for Strings passed from AbstractAccountAuthenticator using Bundle.
-    private static final String[] sStringBundleKeys = new String[] {
-        AccountManager.KEY_ACCOUNT_NAME,
-        AccountManager.KEY_ACCOUNT_TYPE,
-        AccountManager.KEY_AUTHTOKEN,
-        AccountManager.KEY_AUTH_TOKEN_LABEL,
-        AccountManager.KEY_ERROR_MESSAGE,
-        AccountManager.KEY_PASSWORD,
-        AccountManager.KEY_ACCOUNT_STATUS_TOKEN};
-
-    /**
-     * Keep only documented fields in a Bundle received from AbstractAccountAuthenticator.
-     */
-    protected static Bundle sanitizeBundle(Bundle bundle) {
-        if (bundle == null) {
-            return null;
-        }
-        Bundle sanitizedBundle = new Bundle();
-        Bundle.setDefusable(sanitizedBundle, true);
-        int updatedKeysCount = 0;
-        for (String stringKey : sStringBundleKeys) {
-            if (bundle.containsKey(stringKey)) {
-                String value = bundle.getString(stringKey);
-                sanitizedBundle.putString(stringKey, value);
-                updatedKeysCount++;
-            }
-        }
-        String key = AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY;
-        if (bundle.containsKey(key)) {
-            long expiryMillis = bundle.getLong(key, 0L);
-            sanitizedBundle.putLong(key, expiryMillis);
-            updatedKeysCount++;
-        }
-        key = AccountManager.KEY_BOOLEAN_RESULT;
-        if (bundle.containsKey(key)) {
-            boolean booleanResult = bundle.getBoolean(key, false);
-            sanitizedBundle.putBoolean(key, booleanResult);
-            updatedKeysCount++;
-        }
-        key = AccountManager.KEY_ERROR_CODE;
-        if (bundle.containsKey(key)) {
-            int errorCode = bundle.getInt(key, 0);
-            sanitizedBundle.putInt(key, errorCode);
-            updatedKeysCount++;
-        }
-        key = AccountManager.KEY_INTENT;
-        if (bundle.containsKey(key)) {
-            Intent intent = bundle.getParcelable(key, Intent.class);
-            sanitizedBundle.putParcelable(key, intent);
-            updatedKeysCount++;
-        }
-        if (bundle.containsKey(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE)) {
-            // The field is not copied in sanitized bundle.
-            updatedKeysCount++;
-        }
-        if (updatedKeysCount != bundle.size()) {
-            Log.w(TAG, "Size mismatch after sanitizeBundle call.");
-        }
-        return sanitizedBundle;
-    }
-
     private abstract class Session extends IAccountAuthenticatorResponse.Stub
             implements IBinder.DeathRecipient, ServiceConnection {
         private final Object mSessionLock = new Object();
@@ -5304,15 +5229,10 @@
                 }
             }
         }
+
         @Override
         public void onResult(Bundle result) {
             Bundle.setDefusable(result, true);
-            result = sanitizeBundle(result);
-            onResultSkipSanitization(result);
-        }
-
-        public void onResultSkipSanitization(Bundle result) {
-            Bundle.setDefusable(result, true);
             mNumResults++;
             Intent intent = null;
             if (result != null) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ad72941..35323d6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -262,6 +262,7 @@
 import android.content.AttributionSource;
 import android.content.AutofillOptions;
 import android.content.BroadcastReceiver;
+import android.content.ClipData;
 import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
 import android.content.ContentCaptureOptions;
@@ -419,7 +420,6 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.MemInfoReader;
 import com.android.internal.util.Preconditions;
-import com.android.server.crashrecovery.CrashRecoveryHelper;
 import com.android.server.AlarmManagerInternal;
 import com.android.server.BootReceiver;
 import com.android.server.DeviceIdleInternal;
@@ -439,6 +439,7 @@
 import com.android.server.appop.AppOpsService;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.contentcapture.ContentCaptureManagerInternal;
+import com.android.server.crashrecovery.CrashRecoveryHelper;
 import com.android.server.criticalevents.CriticalEventLog;
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.graphics.fonts.FontManagerInternal;
@@ -483,6 +484,7 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 import java.time.Instant;
 import java.time.ZoneId;
 import java.time.ZonedDateTime;
@@ -500,6 +502,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
@@ -19097,4 +19100,87 @@
     Freezer getFreezer() {
         return mFreezer;
     }
+
+    // Set of IntentCreatorToken objects that are currently active.
+    private static final Map<IntentCreatorToken.Key, WeakReference<IntentCreatorToken>>
+            sIntentCreatorTokenCache = new ConcurrentHashMap<>();
+
+    /**
+     * A binder token used to keep track of which app created the intent. This token can be used to
+     * defend against intent redirect attacks. It stores uid of the intent creator and key fields of
+     * the intent to make it impossible for attacker to fake uid with a malicious intent.
+     *
+     * @hide
+     */
+    public static final class IntentCreatorToken extends Binder {
+        @NonNull
+        private final Key mKeyFields;
+
+        public IntentCreatorToken(int creatorUid, Intent intent) {
+            super();
+            this.mKeyFields = new Key(creatorUid, intent);
+        }
+
+        public int getCreatorUid() {
+            return mKeyFields.mCreatorUid;
+        }
+
+        /** {@hide} */
+        public static boolean isValid(@NonNull Intent intent) {
+            IBinder binder = intent.getCreatorToken();
+            IntentCreatorToken token = null;
+            if (binder instanceof IntentCreatorToken) {
+                token = (IntentCreatorToken) binder;
+            }
+            return token != null && token.mKeyFields.equals(
+                    new Key(token.mKeyFields.mCreatorUid, intent));
+        }
+
+        private static class Key {
+            private Key(int creatorUid, Intent intent) {
+                this.mCreatorUid = creatorUid;
+                this.mAction = intent.getAction();
+                this.mData = intent.getData();
+                this.mType = intent.getType();
+                this.mPackage = intent.getPackage();
+                this.mComponent = intent.getComponent();
+                this.mFlags = intent.getFlags() & Intent.IMMUTABLE_FLAGS;
+                ClipData clipData = intent.getClipData();
+                if (clipData != null) {
+                    this.mClipDataUris = new ArrayList<>(clipData.getItemCount());
+                    for (int i = 0; i < clipData.getItemCount(); i++) {
+                        this.mClipDataUris.add(clipData.getItemAt(i).getUri());
+                    }
+                }
+            }
+
+            private final int mCreatorUid;
+            private final String mAction;
+            private final Uri mData;
+            private final String mType;
+            private final String mPackage;
+            private final ComponentName mComponent;
+            private final int mFlags;
+            private List<Uri> mClipDataUris;
+
+            @Override
+            public boolean equals(Object o) {
+                if (this == o) return true;
+                if (o == null || getClass() != o.getClass()) return false;
+                Key key = (Key) o;
+                return mCreatorUid == key.mCreatorUid && mFlags == key.mFlags && Objects.equals(
+                        mAction, key.mAction) && Objects.equals(mData, key.mData)
+                        && Objects.equals(mType, key.mType) && Objects.equals(mPackage,
+                        key.mPackage) && Objects.equals(mComponent, key.mComponent)
+                        && Objects.equals(mClipDataUris, key.mClipDataUris);
+            }
+
+            @Override
+            public int hashCode() {
+                return Objects.hash(mCreatorUid, mAction, mData, mType, mPackage, mComponent,
+                        mFlags,
+                        mClipDataUris);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ae33b83..88907e3 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -5293,6 +5293,17 @@
         }
 
         @Override
+        public IntArray getDisplayIds() {
+            IntArray displayIds = new IntArray();
+            synchronized (mSyncRoot) {
+                mLogicalDisplayMapper.forEachLocked((logicalDisplay -> {
+                    displayIds.add(logicalDisplay.getDisplayIdLocked());
+                }), /* includeDisabled= */ false);
+            }
+            return displayIds;
+        }
+
+        @Override
         public DisplayManagerInternal.DisplayOffloadSession registerDisplayOffloader(
                 int displayId, @NonNull DisplayManagerInternal.DisplayOffloader displayOffloader) {
             if (!mFlags.isDisplayOffloadEnabled()) {
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 9281267..99f7f12 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -23,6 +23,7 @@
 import android.hardware.display.DisplayViewport;
 import android.hardware.input.KeyGestureEvent;
 import android.os.IBinder;
+import android.util.SparseBooleanArray;
 import android.view.InputChannel;
 import android.view.inputmethod.InputMethodSubtype;
 
@@ -45,9 +46,11 @@
 
     /**
      * Called by the power manager to tell the input manager whether it should start
-     * watching for wake events.
+     * watching for wake events on given displays.
+     *
+     * @param displayInteractivities Map of display ids to their current interactive state.
      */
-    public abstract void setInteractive(boolean interactive);
+    public abstract void setDisplayInteractivities(SparseBooleanArray displayInteractivities);
 
     /**
      * Toggles Caps Lock state for input device with specific id.
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 909c47b..f045576 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -96,6 +96,7 @@
 import android.provider.DeviceConfig;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Slog;
@@ -3337,10 +3338,22 @@
         }
 
         @Override
-        public void setInteractive(boolean interactive) {
-            mNative.setInteractive(interactive);
-            mBatteryController.onInteractiveChanged(interactive);
-            mKeyboardBacklightController.onInteractiveChanged(interactive);
+        public void setDisplayInteractivities(SparseBooleanArray displayInteractivities) {
+            boolean globallyInteractive = false;
+            ArraySet<Integer> nonInteractiveDisplays = new ArraySet<>();
+            for (int i = 0; i < displayInteractivities.size(); i++) {
+                final int displayId = displayInteractivities.keyAt(i);
+                final boolean displayInteractive = displayInteractivities.get(displayId);
+                if (displayInteractive) {
+                    globallyInteractive = true;
+                } else {
+                    nonInteractiveDisplays.add(displayId);
+                }
+            }
+            mNative.setNonInteractiveDisplays(
+                    nonInteractiveDisplays.stream().mapToInt(Integer::intValue).toArray());
+            mBatteryController.onInteractiveChanged(globallyInteractive);
+            mKeyboardBacklightController.onInteractiveChanged(globallyInteractive);
         }
 
         // TODO(b/358569822): Remove this method from InputManagerInternal after key gesture
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index d17e256..4404d63 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -141,7 +141,7 @@
 
     void setShowTouches(boolean enabled);
 
-    void setInteractive(boolean interactive);
+    void setNonInteractiveDisplays(int[] displayIds);
 
     void reloadCalibration();
 
@@ -409,7 +409,7 @@
         public native void setShowTouches(boolean enabled);
 
         @Override
-        public native void setInteractive(boolean interactive);
+        public native void setNonInteractiveDisplays(int[] displayIds);
 
         @Override
         public native void reloadCalibration();
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index 8410cff..fe9a859 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -171,7 +171,6 @@
     static final int MAX_DATA_BLOCK_SIZE = 1024 * 100;
 
     public static final int DIGEST_SIZE_BYTES = 32;
-    private static final String OEM_UNLOCK_PROP = "sys.oem_unlock_allowed";
     private static final String FLASH_LOCK_PROP = "ro.boot.flash.locked";
     private static final String FLASH_LOCK_LOCKED = "1";
     private static final String FLASH_LOCK_UNLOCKED = "0";
@@ -275,7 +274,6 @@
             enforceChecksumValidity();
             if (mFrpEnforced) {
                 automaticallyDeactivateFrpIfPossible();
-                setOemUnlockEnabledProperty(doGetOemUnlockEnabled());
                 setOldSettingForBackworkCompatibility(mFrpActive);
             } else {
                 formatIfOemUnlockEnabled();
@@ -303,10 +301,6 @@
         }
     }
 
-    private void setOemUnlockEnabledProperty(boolean oemUnlockEnabled) {
-        setProperty(OEM_UNLOCK_PROP, oemUnlockEnabled ? "1" : "0");
-    }
-
     @Override
     public void onBootPhase(int phase) {
         // Wait for initialization in onStart to finish
@@ -342,7 +336,6 @@
                 formatPartitionLocked(true);
             }
         }
-        setOemUnlockEnabledProperty(enabled);
     }
 
     private void enforceOemUnlockReadPermission() {
@@ -814,17 +807,9 @@
             channel.force(true);
         } catch (IOException e) {
             Slog.e(TAG, "unable to access persistent partition", e);
-            return;
-        } finally {
-            setOemUnlockEnabledProperty(enabled);
         }
     }
 
-    @VisibleForTesting
-    void setProperty(String name, String value) {
-        SystemProperties.set(name, value);
-    }
-
     private boolean doGetOemUnlockEnabled() {
         DataInputStream inputStream;
         try {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 1316df1..b1b1637 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -50,6 +50,7 @@
 import android.app.admin.DevicePolicyEventLogger;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.role.RoleManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
@@ -201,6 +202,9 @@
             Manifest.permission.USE_FULL_SCREEN_INTENT
     );
 
+    private static final String ROLE_SYSTEM_APP_PROTECTION_SERVICE =
+            "android.app.role.SYSTEM_APP_PROTECTION_SERVICE";
+
     final PackageArchiver mPackageArchiver;
 
     private final Context mContext;
@@ -1454,6 +1458,12 @@
                     .createEvent(DevicePolicyEnums.UNINSTALL_PACKAGE)
                     .setAdmin(callerPackageName)
                     .write();
+        } else if (isSystemAppProtectionRoleHolder(snapshot, userId, callingUid)) {
+            // Allow the SYSTEM_APP_PROTECTION_SERVICE role holder to silently uninstall, with a
+            // clean calling identity to get DELETE_PACKAGES permission
+            Binder.withCleanCallingIdentity(() ->
+                    mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags)
+            );
         } else {
             ApplicationInfo appInfo = snapshot.getApplicationInfo(callerPackageName, 0, userId);
             if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P) {
@@ -1475,6 +1485,29 @@
         }
     }
 
+    private Boolean isSystemAppProtectionRoleHolder(
+            @NonNull Computer snapshot, int userId, int callingUid) {
+        if (!Flags.deletePackagesSilentlyBackport()) {
+            return false;
+        }
+        String holderPackageName = Binder.withCleanCallingIdentity(() -> {
+            RoleManager roleManager = mPm.mContext.getSystemService(RoleManager.class);
+            if (roleManager == null) {
+                return null;
+            }
+            List<String> holders = roleManager.getRoleHoldersAsUser(
+                    ROLE_SYSTEM_APP_PROTECTION_SERVICE, UserHandle.of(userId));
+            if (holders.isEmpty()) {
+                return null;
+            }
+            return holders.get(0);
+        });
+        if (holderPackageName == null) {
+            return false;
+        }
+        return snapshot.getPackageUid(holderPackageName, /* flags= */ 0, userId) == callingUid;
+    }
+
     @Override
     public void uninstallExistingPackage(VersionedPackage versionedPackage,
             String callerPackageName, IntentSender statusReceiver, int userId) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index df9f7fb..5fc3e33 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1015,8 +1015,7 @@
                     permission, attributionSource, message, forDataDelivery, startDataDelivery,
                     fromDatasource, attributedOp);
             // Finish any started op if some step in the attribution chain failed.
-            if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED
-                    && result != PermissionChecker.PERMISSION_SOFT_DENIED) {
+            if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) {
                 if (attributedOp == AppOpsManager.OP_NONE) {
                     finishDataDelivery(AppOpsManager.permissionToOpCode(permission),
                             attributionSource.asState(), fromDatasource);
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 303828f..0cdf537 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -53,6 +53,7 @@
 import android.util.EventLog;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.WindowManagerPolicyConstants;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -512,8 +513,17 @@
             }
 
             // Start input as soon as we start waking up or going to sleep.
-            mInputManagerInternal.setInteractive(interactive);
             mInputMethodManagerInternal.setInteractive(interactive);
+            if (!mFlags.isPerDisplayWakeByTouchEnabled()) {
+                // Since wakefulness is a global property in original logic, all displays should
+                // be set to the same interactive state, matching system's global wakefulness
+                SparseBooleanArray displayInteractivities = new SparseBooleanArray();
+                int[] displayIds = mDisplayManagerInternal.getDisplayIds().toArray();
+                for (int displayId : displayIds) {
+                    displayInteractivities.put(displayId, interactive);
+                }
+                mInputManagerInternal.setDisplayInteractivities(displayInteractivities);
+            }
 
             // Notify battery stats.
             try {
diff --git a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
index c6ef89d..fd60e06 100644
--- a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
+++ b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
@@ -42,6 +42,11 @@
             Flags::improveWakelockLatency
     );
 
+    private final FlagState mPerDisplayWakeByTouch = new FlagState(
+            Flags.FLAG_PER_DISPLAY_WAKE_BY_TOUCH,
+            Flags::perDisplayWakeByTouch
+    );
+
     /** Returns whether early-screen-timeout-detector is enabled on not. */
     public boolean isEarlyScreenTimeoutDetectorEnabled() {
         return mEarlyScreenTimeoutDetectorFlagState.isEnabled();
@@ -55,6 +60,13 @@
     }
 
     /**
+     * @return Whether per-display wake by touch is enabled or not.
+     */
+    public boolean isPerDisplayWakeByTouchEnabled() {
+        return mPerDisplayWakeByTouch.isEnabled();
+    }
+
+    /**
      * dumps all flagstates
      * @param pw printWriter
      */
@@ -62,6 +74,7 @@
         pw.println("PowerManagerFlags:");
         pw.println(" " + mEarlyScreenTimeoutDetectorFlagState);
         pw.println(" " + mImproveWakelockLatency);
+        pw.println(" " + mPerDisplayWakeByTouch);
     }
 
     private static class FlagState {
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
index 3581b2f..9cf3bb6 100644
--- a/services/core/java/com/android/server/power/feature/power_flags.aconfig
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -17,4 +17,12 @@
     description: "Feature flag for tracking the optimizations to improve the latency of acquiring and releasing a wakelock."
     bug: "339590565"
     is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+    name: "per_display_wake_by_touch"
+    namespace: "power"
+    description: "Feature flag to enable per-display wake by touch"
+    bug: "343295183"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index ddbfd70..d7dc459 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -222,7 +222,8 @@
             UserHandle clientUser) {
         final InputConsumerImpl existingConsumer = getInputConsumer(name);
         if (existingConsumer != null && existingConsumer.mClientUser.equals(clientUser)) {
-            throw new IllegalStateException("Existing input consumer found with name: " + name
+            destroyInputConsumer(existingConsumer.mToken);
+            Slog.w(TAG_WM, "Replacing existing input consumer found with name: " + name
                     + ", display: " + mDisplayId + ", user: " + clientUser);
         }
 
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 9da848a..bf623b2 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -24,6 +24,8 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -2040,10 +2042,15 @@
         final boolean isOtherUndefinedMode = otherWindowingMode == WINDOWING_MODE_UNDEFINED;
 
         // An activity type and windowing mode is compatible if they are the exact same type/mode,
-        // or if one of the type/modes is undefined
+        // or if one of the type/modes is undefined. This is with the exception of
+        // freeform/fullscreen where both modes are assumed to be compatible with each other.
         final boolean isCompatibleType = activityType == otherActivityType
                 || isUndefinedType || isOtherUndefinedType;
         final boolean isCompatibleMode = windowingMode == otherWindowingMode
+                || (windowingMode == WINDOWING_MODE_FREEFORM
+                && otherWindowingMode == WINDOWING_MODE_FULLSCREEN)
+                || (windowingMode == WINDOWING_MODE_FULLSCREEN
+                && otherWindowingMode == WINDOWING_MODE_FREEFORM)
                 || isUndefinedMode || isOtherUndefinedMode;
 
         return isCompatibleType && isCompatibleMode;
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 5cd117b..efca902 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -56,6 +56,7 @@
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include <server_configurable_flags/get_flags.h>
+#include <ui/LogicalDisplayId.h>
 #include <ui/Region.h>
 #include <utils/Log.h>
 #include <utils/Looper.h>
@@ -64,6 +65,7 @@
 
 #include <atomic>
 #include <cinttypes>
+#include <map>
 #include <vector>
 
 #include "android_hardware_display_DisplayViewport.h"
@@ -343,7 +345,7 @@
     void setTouchpadRightClickZoneEnabled(bool enabled);
     void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
     void setShowTouches(bool enabled);
-    void setInteractive(bool interactive);
+    void setNonInteractiveDisplays(const std::set<ui::LogicalDisplayId>& displayIds);
     void reloadCalibration();
     void reloadPointerIcons();
     void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled);
@@ -508,9 +510,11 @@
 
         // Keycodes to be remapped.
         std::map<int32_t /* fromKeyCode */, int32_t /* toKeyCode */> keyRemapping{};
+
+        // Displays which are non-interactive.
+        std::set<ui::LogicalDisplayId> nonInteractiveDisplays;
     } mLocked GUARDED_BY(mLock);
 
-    std::atomic<bool> mInteractive;
     void updateInactivityTimeoutLocked();
     void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
     void ensureSpriteControllerLocked();
@@ -524,12 +528,13 @@
     void forEachPointerControllerLocked(std::function<void(PointerController&)> apply)
             REQUIRES(mLock);
     PointerIcon loadPointerIcon(JNIEnv* env, ui::LogicalDisplayId displayId, PointerIconStyle type);
+    bool isDisplayInteractive(ui::LogicalDisplayId displayId);
 
     static inline JNIEnv* jniEnv() { return AndroidRuntime::getJNIEnv(); }
 };
 
 NativeInputManager::NativeInputManager(jobject serviceObj, const sp<Looper>& looper)
-      : mLooper(looper), mInteractive(true) {
+      : mLooper(looper) {
     JNIEnv* env = jniEnv();
 
     mServiceObj = env->NewGlobalRef(serviceObj);
@@ -547,9 +552,13 @@
 
 void NativeInputManager::dump(std::string& dump) {
     dump += "Input Manager State:\n";
-    dump += StringPrintf(INDENT "Interactive: %s\n", toString(mInteractive.load()));
     { // acquire lock
         std::scoped_lock _l(mLock);
+        auto logicalDisplayIdToString = [](const ui::LogicalDisplayId& displayId) {
+            return std::to_string(displayId.val());
+        };
+        dump += StringPrintf(INDENT "Display not interactive: %s\n",
+                             dumpSet(mLocked.nonInteractiveDisplays, streamableToString).c_str());
         dump += StringPrintf(INDENT "System UI Lights Out: %s\n",
                              toString(mLocked.systemUiLightsOut));
         dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
@@ -1476,8 +1485,10 @@
     mInputManager->getDispatcher().requestPointerCapture(windowToken, enabled);
 }
 
-void NativeInputManager::setInteractive(bool interactive) {
-    mInteractive = interactive;
+void NativeInputManager::setNonInteractiveDisplays(
+        const std::set<ui::LogicalDisplayId>& displayIds) {
+    std::scoped_lock _l(mLock);
+    mLocked.nonInteractiveDisplays = displayIds;
 }
 
 void NativeInputManager::reloadCalibration() {
@@ -1606,7 +1617,7 @@
     // - Ignore untrusted events and pass them along.
     // - Ask the window manager what to do with normal events and trusted injected events.
     // - For normal events wake and brighten the screen if currently off or dim.
-    const bool interactive = mInteractive.load();
+    const bool interactive = isDisplayInteractive(keyEvent.getDisplayId());
     if (interactive) {
         policyFlags |= POLICY_FLAG_INTERACTIVE;
     }
@@ -1644,7 +1655,7 @@
     // - No special filtering for injected events required at this time.
     // - Filter normal events based on screen state.
     // - For normal events brighten (but do not wake) the screen if currently dim.
-    const bool interactive = mInteractive.load();
+    const bool interactive = isDisplayInteractive(displayId);
     if (interactive) {
         policyFlags |= POLICY_FLAG_INTERACTIVE;
     }
@@ -1683,6 +1694,24 @@
     }
 }
 
+bool NativeInputManager::isDisplayInteractive(ui::LogicalDisplayId displayId) {
+    // If an input event doesn't have an associated id, use the default display id
+    if (displayId == ui::LogicalDisplayId::INVALID) {
+        displayId = ui::LogicalDisplayId::DEFAULT;
+    }
+
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+
+        auto it = mLocked.nonInteractiveDisplays.find(displayId);
+        if (it != mLocked.nonInteractiveDisplays.end()) {
+            return false;
+        }
+    } // release lock
+
+    return true;
+}
+
 nsecs_t NativeInputManager::interceptKeyBeforeDispatching(const sp<IBinder>& token,
                                                           const KeyEvent& keyEvent,
                                                           uint32_t policyFlags) {
@@ -2372,10 +2401,17 @@
     im->setShowTouches(enabled);
 }
 
-static void nativeSetInteractive(JNIEnv* env, jobject nativeImplObj, jboolean interactive) {
+static void nativeSetNonInteractiveDisplays(JNIEnv* env, jobject nativeImplObj,
+                                            jintArray displayIds) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
-    im->setInteractive(interactive);
+    const std::vector displayIdsVec = getIntArray(env, displayIds);
+    std::set<ui::LogicalDisplayId> logicalDisplayIds;
+    for (int displayId : displayIdsVec) {
+        logicalDisplayIds.emplace(ui::LogicalDisplayId{displayId});
+    }
+
+    im->setNonInteractiveDisplays(logicalDisplayIds);
 }
 
 static void nativeReloadCalibration(JNIEnv* env, jobject nativeImplObj) {
@@ -3021,7 +3057,7 @@
          (void*)nativeSetShouldNotifyTouchpadHardwareState},
         {"setTouchpadRightClickZoneEnabled", "(Z)V", (void*)nativeSetTouchpadRightClickZoneEnabled},
         {"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
-        {"setInteractive", "(Z)V", (void*)nativeSetInteractive},
+        {"setNonInteractiveDisplays", "([I)V", (void*)nativeSetNonInteractiveDisplays},
         {"reloadCalibration", "()V", (void*)nativeReloadCalibration},
         {"vibrate", "(I[J[III)V", (void*)nativeVibrate},
         {"vibrateCombined", "(I[JLandroid/util/SparseArray;II)V", (void*)nativeVibrateCombined},
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b6a4481..4e89b85 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4152,8 +4152,10 @@
     }
 
     private void checkAllUsersAreAffiliatedWithDevice() {
-        Preconditions.checkCallAuthorization(areAllUsersAffiliatedWithDeviceLocked(),
-                "operation not allowed when device has unaffiliated users");
+        synchronized (getLockObject()) {
+            Preconditions.checkCallAuthorization(areAllUsersAffiliatedWithDeviceLocked(),
+                    "operation not allowed when device has unaffiliated users");
+        }
     }
 
     @Override
@@ -11362,7 +11364,7 @@
         if (mOwners.hasDeviceOwner()) {
             return false;
         }
-        
+
         final ComponentName profileOwner = getProfileOwnerAsUser(userId);
         if (profileOwner == null) {
             return false;
@@ -11371,7 +11373,7 @@
         if (isManagedProfile(userId)) {
             return false;
         }
-        
+
         return true;
     }
     private void enforceCanQueryLockTaskLocked(ComponentName who, String callerPackageName) {
@@ -18213,6 +18215,7 @@
         return false;
     }
 
+    @GuardedBy("getLockObject()")
     private boolean areAllUsersAffiliatedWithDeviceLocked() {
         return mInjector.binderWithCleanCallingIdentity(() -> {
             final List<UserInfo> userInfos = mUserManager.getAliveUsers();
@@ -18310,10 +18313,12 @@
 
         final CallerIdentity caller = getCallerIdentity(admin, packageName);
         if (isPermissionCheckFlagEnabled()) {
-            Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
-                    || areAllUsersAffiliatedWithDeviceLocked());
-            enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(),
-                    UserHandle.USER_ALL);
+            synchronized (getLockObject()) {
+                Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
+                        || areAllUsersAffiliatedWithDeviceLocked());
+                enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(),
+                        UserHandle.USER_ALL);
+            }
         } else {
             if (admin != null) {
                 Preconditions.checkCallAuthorization(
@@ -18325,8 +18330,10 @@
                         isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
             }
 
-            Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
-                    || areAllUsersAffiliatedWithDeviceLocked());
+            synchronized (getLockObject()) {
+                Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
+                        || areAllUsersAffiliatedWithDeviceLocked());
+            }
         }
 
         DevicePolicyEventLogger
@@ -24540,7 +24547,8 @@
             }
         });
     }
-    
+
+    @GuardedBy("getLockObject()")
     private void migrateUserControlDisabledPackagesLocked() {
         Binder.withCleanCallingIdentity(() -> {
             List<UserInfo> users = mUserManager.getUsers();
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
index b21c349..2144785 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
@@ -94,6 +94,7 @@
             ParsedService::getIntents,
             ParsedService::getProperties,
             Intent::getCategories,
+            Intent::getExtraIntentKeys,
             PackageUserState::getDisabledComponents,
             PackageUserState::getEnabledComponents,
             PackageUserState::getSharedLibraryOverlayPaths,
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 fc4d8d8..0702926 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.power;
 
+import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
+import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
+
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
@@ -31,11 +34,13 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.SensorManager;
 import android.hardware.display.AmbientDisplayConfiguration;
+import android.hardware.display.DisplayManagerInternal;
 import android.os.BatteryStats;
 import android.os.Handler;
 import android.os.IWakeLockCallback;
@@ -48,11 +53,18 @@
 import android.os.test.TestLooper;
 import android.provider.Settings;
 import android.testing.TestableContext;
+import android.util.IntArray;
+import android.util.SparseBooleanArray;
+import android.view.Display;
+import android.view.DisplayAddress;
+import android.view.DisplayInfo;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.app.IBatteryStats;
 import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
+import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.power.batterysaver.BatterySaverStateMachine;
 import com.android.server.power.feature.PowerManagerFlags;
@@ -71,6 +83,8 @@
 public class NotifierTest {
     private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
     private static final int USER_ID = 0;
+    private static final int DISPLAY_PORT = 0xFF;
+    private static final long DISPLAY_MODEL = 0xEEEEEEEEL;
 
     @Mock private BatterySaverStateMachine mBatterySaverStateMachineMock;
     @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
@@ -81,10 +95,16 @@
     @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
     @Mock private Vibrator mVibrator;
     @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
+    @Mock private InputManagerInternal mInputManagerInternal;
+    @Mock private InputMethodManagerInternal mInputMethodManagerInternal;
+    @Mock private DisplayManagerInternal mDisplayManagerInternal;
+    @Mock private ActivityManagerInternal mActivityManagerInternal;
     @Mock private WakeLockLog mWakeLockLog;
 
     @Mock private IBatteryStats mBatteryStats;
 
+    @Mock private WindowManagerPolicy mPolicy;
+
     @Mock private PowerManagerFlags mPowerManagerFlags;
 
     @Mock private AppOpsManager mAppOpsManager;
@@ -96,6 +116,8 @@
     private FakeExecutor mTestExecutor = new FakeExecutor();
     private Notifier mNotifier;
 
+    private DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -103,11 +125,25 @@
         LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
         LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal);
 
+        LocalServices.removeServiceForTest(InputManagerInternal.class);
+        LocalServices.addService(InputManagerInternal.class, mInputManagerInternal);
+        LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
+        LocalServices.addService(InputMethodManagerInternal.class, mInputMethodManagerInternal);
+
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInternal);
+
+        mDefaultDisplayInfo.address = DisplayAddress.fromPortAndModel(DISPLAY_PORT, DISPLAY_MODEL);
+        LocalServices.removeServiceForTest(DisplayManagerInternal.class);
+        LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternal);
+
         mContextSpy = spy(new TestableContext(InstrumentationRegistry.getContext()));
         mResourcesSpy = spy(mContextSpy.getResources());
         when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
         when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
         when(mContextSpy.getSystemService(Vibrator.class)).thenReturn(mVibrator);
+        when(mDisplayManagerInternal.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(
+                mDefaultDisplayInfo);
 
         mService = new PowerManagerService(mContextSpy, mInjector);
     }
@@ -232,6 +268,32 @@
     }
 
     @Test
+    public void testOnGlobalWakefulnessChangeStarted() throws Exception {
+        createNotifier();
+        // GIVEN system is currently non-interactive
+        when(mPowerManagerFlags.isPerDisplayWakeByTouchEnabled()).thenReturn(false);
+        final int displayId1 = 101;
+        final int displayId2 = 102;
+        final int[] displayIds = new int[]{displayId1, displayId2};
+        when(mDisplayManagerInternal.getDisplayIds()).thenReturn(IntArray.wrap(displayIds));
+        mNotifier.onGlobalWakefulnessChangeStarted(WAKEFULNESS_ASLEEP,
+                PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, /* eventTime= */ 1000);
+        mTestLooper.dispatchAll();
+
+        // WHEN a global wakefulness change to interactive starts
+        mNotifier.onGlobalWakefulnessChangeStarted(WAKEFULNESS_AWAKE,
+                PowerManager.WAKE_REASON_TAP, /* eventTime= */ 2000);
+        mTestLooper.dispatchAll();
+
+        // THEN input is notified of all displays being interactive
+        final SparseBooleanArray expectedDisplayInteractivities = new SparseBooleanArray();
+        expectedDisplayInteractivities.put(displayId1, true);
+        expectedDisplayInteractivities.put(displayId2, true);
+        verify(mInputManagerInternal).setDisplayInteractivities(expectedDisplayInteractivities);
+        verify(mInputMethodManagerInternal).setInteractive(/* interactive= */ true);
+    }
+
+    @Test
     public void testOnWakeLockListener_RemoteException_NoRethrow() throws RemoteException {
         when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true);
         createNotifier();
@@ -551,7 +613,7 @@
                 mContextSpy,
                 mBatteryStats,
                 mInjector.createSuspendBlocker(mService, "testBlocker"),
-                null,
+                mPolicy,
                 null,
                 null,
                 mTestExecutor, mPowerManagerFlags, injector);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 6e6d5a8..8dfd54f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -174,8 +174,8 @@
     @Mock private AccessibilityTrace mMockA11yTrace;
     @Mock private WindowManagerInternal mMockWindowManagerInternal;
     @Mock private SystemActionPerformer mMockSystemActionPerformer;
-    @Mock private IBinder mMockService;
-    @Mock private IAccessibilityServiceClient mMockServiceInterface;
+    @Mock private IBinder mMockClientBinder;
+    @Mock private IAccessibilityServiceClient mMockClient;
     @Mock private KeyEventDispatcher mMockKeyEventDispatcher;
     @Mock private IAccessibilityInteractionConnection mMockIA11yInteractionConnection;
     @Mock private IAccessibilityInteractionConnectionCallback mMockCallback;
@@ -247,9 +247,9 @@
                 mSpyServiceInfo, SERVICE_ID, mHandler, new Object(), mMockSecurityPolicy,
                 mMockSystemSupport, mMockA11yTrace, mMockWindowManagerInternal,
                 mMockSystemActionPerformer, mMockA11yWindowManager);
-        // Assume that the service is connected
-        mServiceConnection.mService = mMockService;
-        mServiceConnection.mServiceInterface = mMockServiceInterface;
+        // Assume that the client is connected
+        mServiceConnection.mClientBinder = mMockClientBinder;
+        mServiceConnection.mClient = mMockClient;
 
         // Update security policy for this service
         when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(true);
@@ -273,7 +273,7 @@
         final KeyEvent mockKeyEvent = mock(KeyEvent.class);
 
         mServiceConnection.onKeyEvent(mockKeyEvent, sequenceNumber);
-        verify(mMockServiceInterface).onKeyEvent(mockKeyEvent, sequenceNumber);
+        verify(mMockClient).onKeyEvent(mockKeyEvent, sequenceNumber);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index 566feb7..7481fc8 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -63,8 +63,11 @@
 import android.Manifest;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceClient;
+import android.annotation.NonNull;
 import android.app.PendingIntent;
 import android.app.RemoteAction;
+import android.app.admin.DevicePolicyManager;
+import android.app.ecm.EnhancedConfirmationManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -212,6 +215,7 @@
     @Mock private FullScreenMagnificationController mMockFullScreenMagnificationController;
     @Mock private ProxyManager mProxyManager;
     @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
+    @Mock private DevicePolicyManager mDevicePolicyManager;
     @Spy private IUserInitializationCompleteCallback mUserInitializationCompleteCallback;
     @Captor private ArgumentCaptor<Intent> mIntentArgumentCaptor;
     private IAccessibilityManager mA11yManagerServiceOnDevice;
@@ -241,6 +245,7 @@
                 UserManagerInternal.class, mMockUserManagerInternal);
         LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal);
         mInputFilter = mock(FakeInputFilter.class);
+        mTestableContext.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager);
 
         when(mMockMagnificationController.getMagnificationConnectionManager()).thenReturn(
                 mMockMagnificationConnectionManager);
@@ -2160,6 +2165,24 @@
                 .isEqualTo(SOFTWARE);
     }
 
+    @Test
+    @EnableFlags({android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED,
+            android.security.Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS})
+    public void isAccessibilityTargetAllowed_nonSystemUserId_useEcmWithNonSystemUserId() {
+        String fakePackageName = "FAKE_PACKAGE_NAME";
+        int uid = 0; // uid is not used in the actual implementation when flags are on
+        int userId = mTestableContext.getUserId() + 1234;
+        when(mDevicePolicyManager.getPermittedAccessibilityServices(userId)).thenReturn(
+                List.of(fakePackageName));
+        Context mockUserContext = mock(Context.class);
+        mTestableContext.addMockUserContext(userId, mockUserContext);
+
+        mA11yms.isAccessibilityTargetAllowed(fakePackageName, uid, userId);
+
+        verify(mockUserContext).getSystemService(EnhancedConfirmationManager.class);
+    }
+
+
     private Set<String> readStringsFromSetting(String setting) {
         final Set<String> result = new ArraySet<>();
         mA11yms.readColonDelimitedSettingToSet(
@@ -2280,6 +2303,7 @@
 
         private final Context mMockContext;
         private final Map<String, List<BroadcastReceiver>> mBroadcastReceivers = new ArrayMap<>();
+        private ArrayMap<Integer, Context> mMockUserContexts = new ArrayMap<>();
 
         A11yTestableContext(Context base) {
             super(base);
@@ -2317,6 +2341,19 @@
             return mMockContext;
         }
 
+        public void addMockUserContext(int userId, Context context) {
+            mMockUserContexts.put(userId, context);
+        }
+
+        @Override
+        @NonNull
+        public Context createContextAsUser(UserHandle user, int flags) {
+            if (mMockUserContexts.containsKey(user.getIdentifier())) {
+                return mMockUserContexts.get(user.getIdentifier());
+            }
+            return super.createContextAsUser(user, flags);
+        }
+
         Map<String, List<BroadcastReceiver>> getBroadcastReceivers() {
             return mBroadcastReceivers;
         }
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index b9ce8ad..0c92abc 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -1163,6 +1163,16 @@
 
         verify(mMockAccountManagerResponse).onResult(mBundleCaptor.capture());
         Bundle result = mBundleCaptor.getValue();
+        Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
+        assertNotNull(sessionBundle);
+        // Assert that session bundle is decrypted and hence data is visible.
+        assertEquals(AccountManagerServiceTestFixtures.SESSION_DATA_VALUE_1,
+                sessionBundle.getString(AccountManagerServiceTestFixtures.SESSION_DATA_NAME_1));
+        // Assert finishSessionAsUser added calling uid and pid into the sessionBundle
+        assertTrue(sessionBundle.containsKey(AccountManager.KEY_CALLER_UID));
+        assertTrue(sessionBundle.containsKey(AccountManager.KEY_CALLER_PID));
+        assertEquals(sessionBundle.getString(
+                AccountManager.KEY_ANDROID_PACKAGE_NAME), "APCT.package");
 
         // Verify response data
         assertNull(result.getString(AccountManager.KEY_AUTHTOKEN, null));
@@ -2111,6 +2121,12 @@
                 result.getString(AccountManager.KEY_ACCOUNT_NAME));
         assertEquals(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
                 result.getString(AccountManager.KEY_ACCOUNT_TYPE));
+
+        Bundle optionBundle = result.getParcelable(
+                AccountManagerServiceTestFixtures.KEY_OPTIONS_BUNDLE);
+        // Assert addAccountAsUser added calling uid and pid into the option bundle
+        assertTrue(optionBundle.containsKey(AccountManager.KEY_CALLER_UID));
+        assertTrue(optionBundle.containsKey(AccountManager.KEY_CALLER_PID));
     }
 
     @SmallTest
@@ -3441,52 +3457,6 @@
                 + (readTotalTime.doubleValue() / readerCount / loopSize));
     }
 
-    @SmallTest
-    public void testSanitizeBundle_expectedFields() throws Exception {
-        Bundle bundle = new Bundle();
-        bundle.putString(AccountManager.KEY_ACCOUNT_NAME, "name");
-        bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, "type");
-        bundle.putString(AccountManager.KEY_AUTHTOKEN, "token");
-        bundle.putString(AccountManager.KEY_AUTH_TOKEN_LABEL, "label");
-        bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "error message");
-        bundle.putString(AccountManager.KEY_PASSWORD, "password");
-        bundle.putString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN, "status");
-
-        bundle.putLong(AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY, 123L);
-        bundle.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
-        bundle.putInt(AccountManager.KEY_ERROR_CODE, 456);
-
-        Bundle sanitizedBundle = AccountManagerService.sanitizeBundle(bundle);
-        assertEquals(sanitizedBundle.getString(AccountManager.KEY_ACCOUNT_NAME), "name");
-        assertEquals(sanitizedBundle.getString(AccountManager.KEY_ACCOUNT_TYPE), "type");
-        assertEquals(sanitizedBundle.getString(AccountManager.KEY_AUTHTOKEN), "token");
-        assertEquals(sanitizedBundle.getString(AccountManager.KEY_AUTH_TOKEN_LABEL), "label");
-        assertEquals(sanitizedBundle.getString(AccountManager.KEY_ERROR_MESSAGE), "error message");
-        assertEquals(sanitizedBundle.getString(AccountManager.KEY_PASSWORD), "password");
-        assertEquals(sanitizedBundle.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN), "status");
-
-        assertEquals(sanitizedBundle.getLong(
-                AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY, 0), 123L);
-        assertEquals(sanitizedBundle.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false), true);
-        assertEquals(sanitizedBundle.getInt(AccountManager.KEY_ERROR_CODE, 0), 456);
-    }
-
-    @SmallTest
-    public void testSanitizeBundle_filtersUnexpectedFields() throws Exception {
-        Bundle bundle = new Bundle();
-        bundle.putString(AccountManager.KEY_ACCOUNT_NAME, "name");
-        bundle.putString("unknown_key", "value");
-        Bundle sessionBundle = new Bundle();
-        bundle.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
-
-        Bundle sanitizedBundle = AccountManagerService.sanitizeBundle(bundle);
-
-        assertEquals(sanitizedBundle.getString(AccountManager.KEY_ACCOUNT_NAME), "name");
-        assertFalse(sanitizedBundle.containsKey("unknown_key"));
-        // It is a valid response from Authenticator which will be accessed using original Bundle
-        assertFalse(sanitizedBundle.containsKey(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE));
-    }
-
     private void waitForCyclicBarrier(CyclicBarrier cyclicBarrier) {
         try {
             cyclicBarrier.await(LATCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
diff --git a/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java b/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java
index f91f77a..cdfc521 100644
--- a/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pdb/PersistentDataBlockServiceTest.java
@@ -86,7 +86,6 @@
     private File mDataBlockFile;
     private File mFrpSecretFile;
     private File mFrpSecretTmpFile;
-    private String mOemUnlockPropertyValue;
     private boolean mIsUpgradingFromPreV = false;
 
     @Mock private UserManager mUserManager;
@@ -105,13 +104,6 @@
         }
 
         @Override
-        void setProperty(String key, String value) {
-            // Override to capture the value instead of actually setting the property.
-            assertThat(key).isEqualTo("sys.oem_unlock_allowed");
-            mOemUnlockPropertyValue = value;
-        }
-
-        @Override
         boolean isUpgradingFromPreVRelease() {
             return mIsUpgradingFromPreV;
         }
@@ -598,7 +590,6 @@
 
         mInterface.setOemUnlockEnabled(true);
         assertThat(mInterface.getOemUnlockEnabled()).isTrue();
-        assertThat(mOemUnlockPropertyValue).isEqualTo("1");
     }
 
     @Test
@@ -635,7 +626,6 @@
         // The current implementation does not check digest before set or get the oem unlock bit.
         tamperWithDigest();
         mInterface.setOemUnlockEnabled(true);
-        assertThat(mOemUnlockPropertyValue).isEqualTo("1");
         tamperWithDigest();
         assertThat(mInterface.getOemUnlockEnabled()).isTrue();
     }
@@ -676,7 +666,6 @@
 
         mInternalInterface.forceOemUnlockEnabled(true);
 
-        assertThat(mOemUnlockPropertyValue).isEqualTo("1");
         assertThat(readBackingFile(mPdbService.getOemUnlockDataOffset(), 1).array())
                 .isEqualTo(new byte[] { 1 });
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 63983437..92205f39 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -230,7 +230,7 @@
         mDisplayContent.setIgnoreOrientationRequest(enabled);
     }
 
-    void setTopOrganizedTaskAsTopTask() {
+    void setTopActivityOrganizedTask() {
         doReturn(mTaskStack.top()).when(mActivityStack.top()).getOrganizedTask();
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
index 3f34b81..d8373c5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatUtilsTest.java
@@ -150,7 +150,7 @@
             robot.applyOnActivity((a) -> {
                 a.createActivityWithComponentInNewTask();
                 a.setIgnoreOrientationRequest(true);
-                a.setTopOrganizedTaskAsTopTask();
+                a.setTopActivityOrganizedTask();
                 a.setTopActivityInSizeCompatMode(true);
                 a.setTopActivityVisible(true);
             });
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index e0344d7..df17cd1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -432,6 +432,29 @@
     }
 
     @Test
+    public void testAddTaskCompatibleWindowingMode_withFreeformAndFullscreen_expectRemove() {
+        Task task1 = createTaskBuilder(".Task1")
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .build();
+        doReturn(WINDOWING_MODE_FREEFORM).when(task1).getWindowingMode();
+        mRecentTasks.add(task1);
+        mCallbacksRecorder.clear();
+
+        Task task2 = createTaskBuilder(".Task1")
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .build();
+        assertEquals(WINDOWING_MODE_FULLSCREEN, task2.getWindowingMode());
+        mRecentTasks.add(task2);
+
+        assertThat(mCallbacksRecorder.mAdded).hasSize(1);
+        assertThat(mCallbacksRecorder.mAdded).contains(task2);
+        assertThat(mCallbacksRecorder.mTrimmed).isEmpty();
+        assertThat(mCallbacksRecorder.mRemoved).hasSize(1);
+        assertThat(mCallbacksRecorder.mRemoved).contains(task1);
+    }
+
+    @Test
     public void testAddTaskIncompatibleWindowingMode_expectNoRemove() {
         Task task1 = createTaskBuilder(".Task1")
                 .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 6c1e1a4..1294945 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -1027,28 +1027,36 @@
             boolean enabled = (mCurrentFunctions & UsbManager.FUNCTION_MIDI) != 0;
             if (enabled != mMidiEnabled) {
                 if (enabled) {
+                    boolean midiDeviceFound = false;
                     if (android.hardware.usb.flags.Flags.enableUsbSysfsMidiIdentification()) {
                         try {
                             getMidiCardDevice();
+                            midiDeviceFound = true;
                         } catch (FileNotFoundException e) {
-                            Slog.e(TAG, "could not identify MIDI device", e);
-                            enabled = false;
+                            Slog.w(TAG, "could not identify MIDI device", e);
                         }
-                    } else {
+                    }
+                    // For backward compatibility with older kernels without
+                    // https://lore.kernel.org/r/20240307030922.3573161-1-royluo@google.com
+                    if (!midiDeviceFound) {
                         Scanner scanner = null;
                         try {
                             scanner = new Scanner(new File(MIDI_ALSA_PATH));
                             mMidiCard = scanner.nextInt();
                             mMidiDevice = scanner.nextInt();
+                            midiDeviceFound = true;
                         } catch (FileNotFoundException e) {
                             Slog.e(TAG, "could not open MIDI file", e);
-                            enabled = false;
                         } finally {
                             if (scanner != null) {
                                 scanner.close();
                             }
                         }
                     }
+                    if (!midiDeviceFound) {
+                        Slog.e(TAG, "Failed to enable MIDI function");
+                        enabled = false;
+                    }
                 }
                 mMidiEnabled = enabled;
             }
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index df1d51e..064b461 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -346,6 +346,21 @@
             value->value->Accept(&body_printer);
             printer->Undent();
           }
+          printer->Println("Flag disabled values:");
+          for (const auto& value : entry.flag_disabled_values) {
+            printer->Print("(");
+            printer->Print(value->config.to_string());
+            printer->Print(") ");
+            value->value->Accept(&headline_printer);
+            if (options.show_sources && !value->value->GetSource().path.empty()) {
+              printer->Print(" src=");
+              printer->Print(value->value->GetSource().to_string());
+            }
+            printer->Println();
+            printer->Indent();
+            value->value->Accept(&body_printer);
+            printer->Undent();
+          }
           printer->Undent();
         }
       }
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index a274f04..0d261ab 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -71,6 +71,17 @@
 
 enum class FlagStatus { NoFlag = 0, Disabled = 1, Enabled = 2 };
 
+struct FeatureFlagAttribute {
+  std::string name;
+  bool negated = false;
+
+  std::string ToString() {
+    return (negated ? "!" : "") + name;
+  }
+
+  bool operator==(const FeatureFlagAttribute& o) const = default;
+};
+
 android::StringPiece to_string(ResourceType type);
 
 /**
@@ -232,6 +243,12 @@
 
   // Exported symbols
   std::vector<SourcedResourceName> exported_symbols;
+
+  // Flag status
+  FlagStatus flag_status = FlagStatus::NoFlag;
+
+  // Flag
+  std::optional<FeatureFlagAttribute> flag;
 };
 
 /**
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index a5aecc8..fce6aa7 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -107,9 +107,10 @@
   Visibility::Level visibility_level = Visibility::Level::kUndefined;
   bool staged_api = false;
   bool allow_new = false;
-  FlagStatus flag_status = FlagStatus::NoFlag;
   std::optional<OverlayableItem> overlayable_item;
   std::optional<StagedId> staged_alias;
+  std::optional<FeatureFlagAttribute> flag;
+  FlagStatus flag_status;
 
   std::string comment;
   std::unique_ptr<Value> value;
@@ -151,6 +152,7 @@
   }
 
   if (res->value != nullptr) {
+    res->value->SetFlag(res->flag);
     res->value->SetFlagStatus(res->flag_status);
     // Attach the comment, source and config to the value.
     res->value->SetComment(std::move(res->comment));
@@ -162,8 +164,6 @@
     res_builder.SetStagedId(res->staged_alias.value());
   }
 
-  res_builder.SetFlagStatus(res->flag_status);
-
   bool error = false;
   if (!res->name.entry.empty()) {
     if (!table->AddResource(res_builder.Build(), diag)) {
@@ -546,12 +546,26 @@
       {"symbol", std::mem_fn(&ResourceParser::ParseSymbol)},
   });
 
-  std::string resource_type = parser->element_name();
-  auto flag_status = GetFlagStatus(parser);
-  if (!flag_status) {
-    return false;
+  std::string_view resource_type = parser->element_name();
+  if (auto flag = ParseFlag(xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag"))) {
+    if (options_.flag) {
+      diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
+                   << "Resource flag are not allowed both in the path and in the file");
+      return false;
+    }
+    out_resource->flag = std::move(flag);
+    std::string error;
+    auto flag_status = GetFlagStatus(out_resource->flag, options_.feature_flag_values, &error);
+    if (flag_status) {
+      out_resource->flag_status = flag_status.value();
+    } else {
+      diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number())) << error);
+      return false;
+    }
+  } else if (options_.flag) {
+    out_resource->flag = options_.flag;
+    out_resource->flag_status = options_.flag_status;
   }
-  out_resource->flag_status = flag_status.value();
 
   // The value format accepted for this resource.
   uint32_t resource_format = 0u;
@@ -567,7 +581,7 @@
 
     // Items have their type encoded in the type attribute.
     if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
-      resource_type = std::string(maybe_type.value());
+      resource_type = maybe_type.value();
     } else {
       diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
                    << "<item> must have a 'type' attribute");
@@ -590,7 +604,7 @@
 
     // Bags have their type encoded in the type attribute.
     if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
-      resource_type = std::string(maybe_type.value());
+      resource_type = maybe_type.value();
     } else {
       diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
                    << "<bag> must have a 'type' attribute");
@@ -733,33 +747,6 @@
   return false;
 }
 
-std::optional<FlagStatus> ResourceParser::GetFlagStatus(xml::XmlPullParser* parser) {
-  auto flag_status = FlagStatus::NoFlag;
-
-  std::optional<StringPiece> flag = xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag");
-  if (flag) {
-    auto flag_it = options_.feature_flag_values.find(flag.value());
-    if (flag_it == options_.feature_flag_values.end()) {
-      diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
-                   << "Resource flag value undefined");
-      return {};
-    }
-    const auto& flag_properties = flag_it->second;
-    if (!flag_properties.read_only) {
-      diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
-                   << "Only read only flags may be used with resources");
-      return {};
-    }
-    if (!flag_properties.enabled.has_value()) {
-      diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
-                   << "Only flags with a value may be used with resources");
-      return {};
-    }
-    flag_status = flag_properties.enabled.value() ? FlagStatus::Enabled : FlagStatus::Disabled;
-  }
-  return flag_status;
-}
-
 bool ResourceParser::ParseItem(xml::XmlPullParser* parser,
                                ParsedResource* out_resource,
                                const uint32_t format) {
@@ -1666,21 +1653,25 @@
     const std::string& element_namespace = parser->element_namespace();
     const std::string& element_name = parser->element_name();
     if (element_namespace.empty() && element_name == "item") {
-      auto flag_status = GetFlagStatus(parser);
-      if (!flag_status) {
-        error = true;
-        continue;
-      }
+      auto flag = ParseFlag(xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag"));
       std::unique_ptr<Item> item = ParseXml(parser, typeMask, kNoRawString);
       if (!item) {
         diag_->Error(android::DiagMessage(item_source) << "could not parse array item");
         error = true;
         continue;
       }
-      item->SetFlagStatus(flag_status.value());
+      item->SetFlag(flag);
+      std::string err;
+      auto status = GetFlagStatus(flag, options_.feature_flag_values, &err);
+      if (status) {
+        item->SetFlagStatus(status.value());
+      } else {
+        diag_->Error(android::DiagMessage(item_source) << err);
+        error = true;
+        continue;
+      }
       item->SetSource(item_source);
       array->elements.emplace_back(std::move(item));
-
     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
       diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
                    << "unknown tag <" << element_namespace << ":" << element_name << ">");
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 442dea8..90690d5 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -57,6 +57,11 @@
   std::optional<Visibility::Level> visibility;
 
   FeatureFlagValues feature_flag_values;
+
+  // The flag that should be applied to all resources parsed
+  std::optional<FeatureFlagAttribute> flag;
+
+  FlagStatus flag_status = FlagStatus::NoFlag;
 };
 
 struct FlattenedXmlSubTree {
@@ -85,8 +90,6 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(ResourceParser);
 
-  std::optional<FlagStatus> GetFlagStatus(xml::XmlPullParser* parser);
-
   std::optional<FlattenedXmlSubTree> CreateFlattenSubTree(xml::XmlPullParser* parser);
 
   // Parses the XML subtree as a StyleString (flattened XML representation for strings with
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 9751459..5435cba2 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -101,6 +101,21 @@
   }
 };
 
+struct ConfigFlagKey {
+  const ConfigDescription* config;
+  StringPiece product;
+  const FeatureFlagAttribute& flag;
+};
+
+struct lt_config_flag_key_ref {
+  template <typename T>
+  bool operator()(const T& lhs, const ConfigFlagKey& rhs) const noexcept {
+    return std::tie(lhs->config, lhs->product, lhs->value->GetFlag()->name,
+                    lhs->value->GetFlag()->negated) <
+           std::tie(*rhs.config, rhs.product, rhs.flag.name, rhs.flag.negated);
+  }
+};
+
 }  // namespace
 
 ResourceTable::ResourceTable(ResourceTable::Validation validation) : validation_(validation) {
@@ -213,6 +228,25 @@
   return results;
 }
 
+ResourceConfigValue* ResourceEntry::FindOrCreateFlagDisabledValue(
+    const FeatureFlagAttribute& flag, const android::ConfigDescription& config,
+    android::StringPiece product) {
+  auto iter = std::lower_bound(flag_disabled_values.begin(), flag_disabled_values.end(),
+                               ConfigFlagKey{&config, product, flag}, lt_config_flag_key_ref());
+  if (iter != flag_disabled_values.end()) {
+    ResourceConfigValue* value = iter->get();
+    const auto value_flag = value->value->GetFlag().value();
+    if (value_flag.name == flag.name && value_flag.negated == flag.negated &&
+        value->config == config && value->product == product) {
+      return value;
+    }
+  }
+  ResourceConfigValue* newValue =
+      flag_disabled_values.insert(iter, util::make_unique<ResourceConfigValue>(config, product))
+          ->get();
+  return newValue;
+}
+
 bool ResourceEntry::HasDefaultValue() const {
   // The default config should be at the top of the list, since the list is sorted.
   return !values.empty() && values.front()->config == ConfigDescription::DefaultConfig();
@@ -375,13 +409,14 @@
   }
 };
 
-void InsertEntryIntoTableView(ResourceTableView& table, const ResourceTablePackage* package,
-                              const ResourceTableType* type, const std::string& entry_name,
-                              const std::optional<ResourceId>& id, const Visibility& visibility,
-                              const std::optional<AllowNew>& allow_new,
-                              const std::optional<OverlayableItem>& overlayable_item,
-                              const std::optional<StagedId>& staged_id,
-                              const std::vector<std::unique_ptr<ResourceConfigValue>>& values) {
+void InsertEntryIntoTableView(
+    ResourceTableView& table, const ResourceTablePackage* package, const ResourceTableType* type,
+    const std::string& entry_name, const std::optional<ResourceId>& id,
+    const Visibility& visibility, const std::optional<AllowNew>& allow_new,
+    const std::optional<OverlayableItem>& overlayable_item,
+    const std::optional<StagedId>& staged_id,
+    const std::vector<std::unique_ptr<ResourceConfigValue>>& values,
+    const std::vector<std::unique_ptr<ResourceConfigValue>>& flag_disabled_values) {
   SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter;
   SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter;
   SortedVectorInserter<ResourceTableEntryView, EntryViewComparer> entry_inserter;
@@ -408,6 +443,9 @@
   for (auto& value : values) {
     new_entry.values.emplace_back(value.get());
   }
+  for (auto& value : flag_disabled_values) {
+    new_entry.flag_disabled_values.emplace_back(value.get());
+  }
 
   entry_inserter.Insert(view_type->entries, std::move(new_entry));
 }
@@ -426,6 +464,21 @@
   return nullptr;
 }
 
+const ResourceConfigValue* ResourceTableEntryView::FindFlagDisabledValue(
+    const FeatureFlagAttribute& flag, const ConfigDescription& config,
+    android::StringPiece product) const {
+  auto iter = std::lower_bound(flag_disabled_values.begin(), flag_disabled_values.end(),
+                               ConfigFlagKey{&config, product, flag}, lt_config_flag_key_ref());
+  if (iter != values.end()) {
+    const ResourceConfigValue* value = *iter;
+    if (value->value->GetFlag() == flag && value->config == config &&
+        StringPiece(value->product) == product) {
+      return value;
+    }
+  }
+  return nullptr;
+}
+
 ResourceTableView ResourceTable::GetPartitionedView(const ResourceTableViewOptions& options) const {
   ResourceTableView view;
   for (const auto& package : packages) {
@@ -433,13 +486,13 @@
       for (const auto& entry : type->entries) {
         InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, entry->id,
                                  entry->visibility, entry->allow_new, entry->overlayable_item,
-                                 entry->staged_id, entry->values);
+                                 entry->staged_id, entry->values, entry->flag_disabled_values);
 
         if (options.create_alias_entries && entry->staged_id) {
           auto alias_id = entry->staged_id.value().id;
           InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, alias_id,
                                    entry->visibility, entry->allow_new, entry->overlayable_item, {},
-                                   entry->values);
+                                   entry->values, entry->flag_disabled_values);
         }
       }
     }
@@ -587,6 +640,25 @@
     entry->staged_id = res.staged_id.value();
   }
 
+  if (res.value != nullptr && res.value->GetFlagStatus() == FlagStatus::Disabled) {
+    auto disabled_config_value =
+        entry->FindOrCreateFlagDisabledValue(res.value->GetFlag().value(), res.config, res.product);
+    if (!disabled_config_value->value) {
+      // Resource does not exist, add it now.
+      // Must clone the value since it might be in the values vector as well
+      CloningValueTransformer cloner(&string_pool);
+      disabled_config_value->value = res.value->Transform(cloner);
+    } else {
+      diag->Error(android::DiagMessage(source)
+                  << "duplicate value for resource '" << res.name << "' " << "with config '"
+                  << res.config << "' and flag '"
+                  << (res.value->GetFlag().value().negated ? "!" : "")
+                  << res.value->GetFlag().value().name << "'");
+      diag->Error(android::DiagMessage(source) << "resource previously defined here");
+      return false;
+    }
+  }
+
   if (res.value != nullptr) {
     auto config_value = entry->FindOrCreateValue(res.config, res.product);
     if (!config_value->value) {
@@ -595,9 +667,9 @@
     } else {
       // When validation is enabled, ensure that a resource cannot have multiple values defined for
       // the same configuration unless protected by flags.
-      auto result =
-          validate ? ResolveFlagCollision(config_value->value->GetFlagStatus(), res.flag_status)
-                   : CollisionResult::kKeepBoth;
+      auto result = validate ? ResolveFlagCollision(config_value->value->GetFlagStatus(),
+                                                    res.value->GetFlagStatus())
+                             : CollisionResult::kKeepBoth;
       if (result == CollisionResult::kConflict) {
         result = ResolveValueCollision(config_value->value.get(), res.value.get());
       }
@@ -771,11 +843,6 @@
   return *this;
 }
 
-NewResourceBuilder& NewResourceBuilder::SetFlagStatus(FlagStatus flag_status) {
-  res_.flag_status = flag_status;
-  return *this;
-}
-
 NewResource NewResourceBuilder::Build() {
   return std::move(res_);
 }
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index cba6b70..b0e1855 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -136,6 +136,9 @@
   // The resource's values for each configuration.
   std::vector<std::unique_ptr<ResourceConfigValue>> values;
 
+  // The resource's values that are behind disabled flags.
+  std::vector<std::unique_ptr<ResourceConfigValue>> flag_disabled_values;
+
   explicit ResourceEntry(android::StringPiece name) : name(name) {
   }
 
@@ -148,6 +151,13 @@
                                          android::StringPiece product);
   std::vector<ResourceConfigValue*> FindAllValues(const android::ConfigDescription& config);
 
+  // Either returns the existing ResourceConfigValue in the disabled list with the given flag,
+  // config, and product or creates a new one and returns that. In either case the returned value
+  // does not have the flag set on the value so it must be set by the caller.
+  ResourceConfigValue* FindOrCreateFlagDisabledValue(const FeatureFlagAttribute& flag,
+                                                     const android::ConfigDescription& config,
+                                                     android::StringPiece product = {});
+
   template <typename Func>
   std::vector<ResourceConfigValue*> FindValuesIf(Func f) {
     std::vector<ResourceConfigValue*> results;
@@ -215,9 +225,14 @@
   std::optional<OverlayableItem> overlayable_item;
   std::optional<StagedId> staged_id;
   std::vector<const ResourceConfigValue*> values;
+  std::vector<const ResourceConfigValue*> flag_disabled_values;
 
   const ResourceConfigValue* FindValue(const android::ConfigDescription& config,
                                        android::StringPiece product = {}) const;
+
+  const ResourceConfigValue* FindFlagDisabledValue(const FeatureFlagAttribute& flag,
+                                                   const android::ConfigDescription& config,
+                                                   android::StringPiece product = {}) const;
 };
 
 struct ResourceTableTypeView {
@@ -269,7 +284,6 @@
   std::optional<AllowNew> allow_new;
   std::optional<StagedId> staged_id;
   bool allow_mangled = false;
-  FlagStatus flag_status = FlagStatus::NoFlag;
 };
 
 struct NewResourceBuilder {
@@ -283,7 +297,6 @@
   NewResourceBuilder& SetAllowNew(AllowNew allow_new);
   NewResourceBuilder& SetStagedId(StagedId id);
   NewResourceBuilder& SetAllowMangled(bool allow_mangled);
-  NewResourceBuilder& SetFlagStatus(FlagStatus flag_status);
   NewResource Build();
 
  private:
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index b75e87c..723cfc0 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -1102,6 +1102,7 @@
 std::unique_ptr<T> CopyValueFields(std::unique_ptr<T> new_value, const T* value) {
   new_value->SetSource(value->GetSource());
   new_value->SetComment(value->GetComment());
+  new_value->SetFlag(value->GetFlag());
   new_value->SetFlagStatus(value->GetFlagStatus());
   return new_value;
 }
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index a1b1839..e000c65 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -65,10 +65,21 @@
     return translatable_;
   }
 
+  void SetFlag(std::optional<FeatureFlagAttribute> val) {
+    flag_ = val;
+  }
+
+  std::optional<FeatureFlagAttribute> GetFlag() const {
+    return flag_;
+  }
+
   void SetFlagStatus(FlagStatus val) {
     flag_status_ = val;
   }
 
+  // If the value is behind a flag this returns whether that flag was enabled when the value was
+  // parsed by comparing it to the flags passed on the command line to aapt2 (taking into account
+  // negation if necessary). If there was no flag, FlagStatus::NoFlag is returned instead.
   FlagStatus GetFlagStatus() const {
     return flag_status_;
   }
@@ -128,6 +139,7 @@
   std::string comment_;
   bool weak_ = false;
   bool translatable_ = true;
+  std::optional<FeatureFlagAttribute> flag_;
   FlagStatus flag_status_ = FlagStatus::NoFlag;
 
  private:
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 5c64089..a0f60b6 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -240,6 +240,9 @@
 
   // The staged resource ID of this finalized resource.
   StagedId staged_id = 7;
+
+  // The set of values defined for this entry which are behind disabled flags
+  repeated ConfigValue flag_disabled_config_value = 8;
 }
 
 // A Configuration/Value pair.
@@ -283,6 +286,8 @@
 
   // The status of the flag the value is behind if any
   uint32 flag_status = 8;
+  bool flag_negated = 9;
+  string flag_name = 10;
 }
 
 // A CompoundValue is an abstract type. It represents a value that is a made of other values.
diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto
index b0ed3da3..f4735a2 100644
--- a/tools/aapt2/ResourcesInternal.proto
+++ b/tools/aapt2/ResourcesInternal.proto
@@ -49,4 +49,9 @@
 
   // Any symbols this file auto-generates/exports (eg. @+id/foo in an XML file).
   repeated Symbol exported_symbol = 5;
+
+  // The status of the flag the file is behind if any
+  uint32 flag_status = 6;
+  bool flag_negated = 7;
+  string flag_name = 8;
 }
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 2a978a5..52372fa 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -67,6 +67,7 @@
   std::string resource_dir;
   std::string name;
   std::string extension;
+  std::string flag_name;
 
   // Original config str. We keep this because when we parse the config, we may add on
   // version qualifiers. We want to preserve the original input so the output is easily
@@ -81,6 +82,22 @@
                                                                std::string* out_error,
                                                                const CompileOptions& options) {
   std::vector<std::string> parts = util::Split(path, dir_sep);
+
+  std::string flag_name;
+  // Check for a flag
+  for (auto iter = parts.begin(); iter != parts.end();) {
+    if (iter->starts_with("flag(") && iter->ends_with(")")) {
+      if (!flag_name.empty()) {
+        if (out_error) *out_error = "resource path cannot contain more than one flag directory";
+        return {};
+      }
+      flag_name = iter->substr(5, iter->size() - 6);
+      iter = parts.erase(iter);
+    } else {
+      ++iter;
+    }
+  }
+
   if (parts.size() < 2) {
     if (out_error) *out_error = "bad resource path";
     return {};
@@ -131,6 +148,7 @@
                           std::string(dir_str),
                           std::string(name),
                           std::string(extension),
+                          std::move(flag_name),
                           std::string(config_str),
                           config};
 }
@@ -142,6 +160,9 @@
     name << "-" << data.config_str;
   }
   name << "_" << data.name;
+  if (!data.flag_name.empty()) {
+    name << ".(" << data.flag_name << ")";
+  }
   if (!data.extension.empty()) {
     name << "." << data.extension;
   }
@@ -163,7 +184,6 @@
                                        << "failed to open file: " << fin->GetError());
       return false;
     }
-
     // Parse the values file from XML.
     xml::XmlPullParser xml_parser(fin.get());
 
@@ -176,6 +196,18 @@
     // If visibility was forced, we need to use it when creating a new resource and also error if
     // we try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags.
     parser_options.visibility = options.visibility;
+    parser_options.flag = ParseFlag(path_data.flag_name);
+
+    if (parser_options.flag) {
+      std::string error;
+      auto flag_status = GetFlagStatus(parser_options.flag, options.feature_flag_values, &error);
+      if (flag_status) {
+        parser_options.flag_status = std::move(flag_status.value());
+      } else {
+        context->GetDiagnostics()->Error(android::DiagMessage(path_data.source) << error);
+        return false;
+      }
+    }
 
     ResourceParser res_parser(context->GetDiagnostics(), &table, path_data.source, path_data.config,
         parser_options);
@@ -402,6 +434,18 @@
   xmlres->file.config = path_data.config;
   xmlres->file.source = path_data.source;
   xmlres->file.type = ResourceFile::Type::kProtoXml;
+  xmlres->file.flag = ParseFlag(path_data.flag_name);
+
+  if (xmlres->file.flag) {
+    std::string error;
+    auto flag_status = GetFlagStatus(xmlres->file.flag, options.feature_flag_values, &error);
+    if (flag_status) {
+      xmlres->file.flag_status = flag_status.value();
+    } else {
+      context->GetDiagnostics()->Error(android::DiagMessage(path_data.source) << error);
+      return false;
+    }
+  }
 
   // Collect IDs that are defined here.
   XmlIdCollector collector;
@@ -491,6 +535,27 @@
   res_file.source = path_data.source;
   res_file.type = ResourceFile::Type::kPng;
 
+  if (!path_data.flag_name.empty()) {
+    FeatureFlagAttribute flag;
+    auto name = path_data.flag_name;
+    if (name.starts_with('!')) {
+      flag.negated = true;
+      flag.name = name.substr(1);
+    } else {
+      flag.name = name;
+    }
+    res_file.flag = flag;
+
+    std::string error;
+    auto flag_status = GetFlagStatus(flag, options.feature_flag_values, &error);
+    if (flag_status) {
+      res_file.flag_status = flag_status.value();
+    } else {
+      context->GetDiagnostics()->Error(android::DiagMessage(path_data.source) << error);
+      return false;
+    }
+  }
+
   {
     auto data = file->OpenAsData();
     if (!data) {
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index 6da3176..d3750a6 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -138,6 +138,22 @@
     }
   }
 
+  for (const ResourceConfigValue* config_value_a : entry_a.flag_disabled_values) {
+    auto config_value_b = entry_b.FindFlagDisabledValue(config_value_a->value->GetFlag().value(),
+                                                        config_value_a->config);
+    if (!config_value_b) {
+      std::stringstream str_stream;
+      str_stream << "missing disabled value " << pkg_a.name << ":" << type_a.named_type << "/"
+                 << entry_a.name << " config=" << config_value_a->config
+                 << " flag=" << config_value_a->value->GetFlag()->ToString();
+      EmitDiffLine(apk_b->GetSource(), str_stream.str());
+      diff = true;
+    } else {
+      diff |= EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a,
+                                          apk_b, pkg_b, type_b, entry_b, config_value_b);
+    }
+  }
+
   // Check for any newly added config values.
   for (const ResourceConfigValue* config_value_b : entry_b.values) {
     auto config_value_a = entry_a.FindValue(config_value_b->config);
@@ -149,6 +165,18 @@
       diff = true;
     }
   }
+  for (const ResourceConfigValue* config_value_b : entry_b.flag_disabled_values) {
+    auto config_value_a = entry_a.FindFlagDisabledValue(config_value_b->value->GetFlag().value(),
+                                                        config_value_b->config);
+    if (!config_value_a) {
+      std::stringstream str_stream;
+      str_stream << "new disabled config " << pkg_b.name << ":" << type_b.named_type << "/"
+                 << entry_b.name << " config=" << config_value_b->config
+                 << " flag=" << config_value_b->value->GetFlag()->ToString();
+      EmitDiffLine(apk_b->GetSource(), str_stream.str());
+      diff = true;
+    }
+  }
   return diff;
 }
 
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index 7739171..08f8f0d 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -34,6 +34,44 @@
 
 namespace aapt {
 
+std::optional<FeatureFlagAttribute> ParseFlag(std::optional<std::string_view> flag_text) {
+  if (!flag_text || flag_text->empty()) {
+    return {};
+  }
+  FeatureFlagAttribute flag;
+  if (flag_text->starts_with('!')) {
+    flag.negated = true;
+    flag.name = flag_text->substr(1);
+  } else {
+    flag.name = flag_text.value();
+  }
+  return flag;
+}
+
+std::optional<FlagStatus> GetFlagStatus(const std::optional<FeatureFlagAttribute>& flag,
+                                        const FeatureFlagValues& feature_flag_values,
+                                        std::string* out_err) {
+  if (!flag) {
+    return FlagStatus::NoFlag;
+  }
+  auto flag_it = feature_flag_values.find(flag->name);
+  if (flag_it == feature_flag_values.end()) {
+    *out_err = "Resource flag value undefined: " + flag->name;
+    return {};
+  }
+  const auto& flag_properties = flag_it->second;
+  if (!flag_properties.read_only) {
+    *out_err = "Only read only flags may be used with resources: " + flag->name;
+    return {};
+  }
+  if (!flag_properties.enabled.has_value()) {
+    *out_err = "Only flags with a value may be used with resources: " + flag->name;
+    return {};
+  }
+  return (flag_properties.enabled.value() != flag->negated) ? FlagStatus::Enabled
+                                                            : FlagStatus::Disabled;
+}
+
 std::optional<uint16_t> ParseTargetDensityParameter(StringPiece arg, android::IDiagnostics* diag) {
   ConfigDescription preferred_density_config;
   if (!ConfigDescription::Parse(arg, &preferred_density_config)) {
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index 6b8813b..d32e532 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -49,6 +49,12 @@
 
 using FeatureFlagValues = std::map<std::string, FeatureFlagProperties, std::less<>>;
 
+std::optional<FeatureFlagAttribute> ParseFlag(std::optional<std::string_view> flag_text);
+
+std::optional<FlagStatus> GetFlagStatus(const std::optional<FeatureFlagAttribute>& flag,
+                                        const FeatureFlagValues& feature_flag_values,
+                                        std::string* out_err);
+
 // Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc).
 // Returns Nothing and logs a human friendly error message if the string was not legal.
 std::optional<uint16_t> ParseTargetDensityParameter(android::StringPiece arg,
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 55f5e56..8583cad 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -536,6 +536,34 @@
 
         config_value->value = DeserializeValueFromPb(pb_config_value.value(), src_pool, config,
                                                      &out_table->string_pool, files, out_error);
+
+        if (config_value->value == nullptr) {
+          return false;
+        }
+      }
+
+      // flag disabled
+      for (const pb::ConfigValue& pb_config_value : pb_entry.flag_disabled_config_value()) {
+        const pb::Configuration& pb_config = pb_config_value.config();
+
+        ConfigDescription config;
+        if (!DeserializeConfigFromPb(pb_config, &config, out_error)) {
+          return false;
+        }
+
+        FeatureFlagAttribute flag;
+        flag.name = pb_config_value.value().item().flag_name();
+        flag.negated = pb_config_value.value().item().flag_negated();
+        ResourceConfigValue* config_value =
+            entry->FindOrCreateFlagDisabledValue(std::move(flag), config, pb_config.product());
+        if (config_value->value != nullptr) {
+          *out_error = "duplicate configuration in resource table";
+          return false;
+        }
+
+        config_value->value = DeserializeValueFromPb(pb_config_value.value(), src_pool, config,
+                                                     &out_table->string_pool, files, out_error);
+
         if (config_value->value == nullptr) {
           return false;
         }
@@ -615,6 +643,12 @@
   out_file->source.path = pb_file.source_path();
   out_file->type = DeserializeFileReferenceTypeFromPb(pb_file.type());
 
+  out_file->flag_status = (FlagStatus)pb_file.flag_status();
+  if (!pb_file.flag_name().empty()) {
+    out_file->flag =
+        FeatureFlagAttribute{.name = pb_file.flag_name(), .negated = pb_file.flag_negated()};
+  }
+
   std::string config_error;
   if (!DeserializeConfigFromPb(pb_file.config(), &out_file->config, &config_error)) {
     std::ostringstream error;
@@ -748,7 +782,6 @@
     if (value == nullptr) {
       return {};
     }
-
   } else if (pb_value.has_compound_value()) {
     const pb::CompoundValue& pb_compound_value = pb_value.compound_value();
     switch (pb_compound_value.value_case()) {
@@ -1018,6 +1051,12 @@
       DeserializeItemFromPbInternal(pb_item, src_pool, config, value_pool, files, out_error);
   if (item) {
     item->SetFlagStatus((FlagStatus)pb_item.flag_status());
+    if (!pb_item.flag_name().empty()) {
+      FeatureFlagAttribute flag;
+      flag.name = pb_item.flag_name();
+      flag.negated = pb_item.flag_negated();
+      item->SetFlag(std::move(flag));
+    }
   }
   return item;
 }
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 5772b3b..d83fe91 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -427,6 +427,14 @@
           SerializeValueToPb(*config_value->value, pb_config_value->mutable_value(),
                              source_pool.get());
         }
+
+        for (const ResourceConfigValue* config_value : entry.flag_disabled_values) {
+          pb::ConfigValue* pb_config_value = pb_entry->add_flag_disabled_config_value();
+          SerializeConfig(config_value->config, pb_config_value->mutable_config());
+          pb_config_value->mutable_config()->set_product(config_value->product);
+          SerializeValueToPb(*config_value->value, pb_config_value->mutable_value(),
+                             source_pool.get());
+        }
       }
     }
   }
@@ -721,6 +729,11 @@
   }
   if (out_value->has_item()) {
     out_value->mutable_item()->set_flag_status((uint32_t)value.GetFlagStatus());
+    if (value.GetFlag()) {
+      const auto& flag = value.GetFlag();
+      out_value->mutable_item()->set_flag_negated(flag->negated);
+      out_value->mutable_item()->set_flag_name(flag->name);
+    }
   }
 }
 
@@ -730,6 +743,11 @@
   item.Accept(&serializer);
   out_item->MergeFrom(value.item());
   out_item->set_flag_status((uint32_t)item.GetFlagStatus());
+  if (item.GetFlag()) {
+    const auto& flag = item.GetFlag();
+    out_item->set_flag_negated(flag->negated);
+    out_item->set_flag_name(flag->name);
+  }
 }
 
 void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file) {
@@ -737,6 +755,11 @@
   out_file->set_source_path(file.source.path);
   out_file->set_type(SerializeFileReferenceTypeToPb(file.type));
   SerializeConfig(file.config, out_file->mutable_config());
+  out_file->set_flag_status((uint32_t)file.flag_status);
+  if (file.flag) {
+    out_file->set_flag_negated(file.flag->negated);
+    out_file->set_flag_name(file.flag->name);
+  }
 
   for (const SourcedResourceName& exported : file.exported_symbols) {
     pb::internal::CompiledFile_Symbol* pb_symbol = out_file->add_exported_symbol();
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
index c456e5c..7160b35 100644
--- a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
@@ -31,13 +31,29 @@
         "res/values/ints.xml",
         "res/values/strings.xml",
         "res/layout/layout1.xml",
+        "res/layout/layout3.xml",
+        "res/flag(test.package.falseFlag)/values/bools.xml",
+        "res/flag(test.package.falseFlag)/layout/layout2.xml",
+        "res/flag(test.package.falseFlag)/drawable/removedpng.png",
+        "res/flag(test.package.trueFlag)/layout/layout3.xml",
+        "res/values/flag(test.package.trueFlag)/bools.xml",
+        "res/values/flag(!test.package.trueFlag)/bools.xml",
+        "res/values/flag(!test.package.falseFlag)/bools.xml",
     ],
     out: [
+        "drawable_removedpng.(test.package.falseFlag).png.flat",
         "values_bools.arsc.flat",
+        "values_bools.(test.package.falseFlag).arsc.flat",
+        "values_bools.(test.package.trueFlag).arsc.flat",
+        "values_bools.(!test.package.falseFlag).arsc.flat",
+        "values_bools.(!test.package.trueFlag).arsc.flat",
         "values_bools2.arsc.flat",
         "values_ints.arsc.flat",
         "values_strings.arsc.flat",
         "layout_layout1.xml.flat",
+        "layout_layout2.(test.package.falseFlag).xml.flat",
+        "layout_layout3.xml.flat",
+        "layout_layout3.(test.package.trueFlag).xml.flat",
     ],
     cmd: "$(location aapt2) compile $(in) -o $(genDir) " +
         "--feature-flags test.package.falseFlag:ro=false,test.package.trueFlag:ro=true",
diff --git "a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/drawable/removedpng.png" "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/drawable/removedpng.png"
new file mode 100644
index 0000000..8a9e698
--- /dev/null
+++ "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/drawable/removedpng.png"
Binary files differ
diff --git "a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/layout/layout2.xml" "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/layout/layout2.xml"
new file mode 100644
index 0000000..dec5de7
--- /dev/null
+++ "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/layout/layout2.xml"
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+</LinearLayout>
\ No newline at end of file
diff --git "a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/values/bools.xml" "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/values/bools.xml"
new file mode 100644
index 0000000..c46c4d4
--- /dev/null
+++ "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.falseFlag\051/values/bools.xml"
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <bool name="bool7">false</bool>
+</resources>
\ No newline at end of file
diff --git "a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.trueFlag\051/layout/layout3.xml" "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.trueFlag\051/layout/layout3.xml"
new file mode 100644
index 0000000..5aeee0e
--- /dev/null
+++ "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/flag\050test.package.trueFlag\051/layout/layout3.xml"
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+    <TextView android:id="@+id/text1"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="foobar" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout3.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout3.xml
new file mode 100644
index 0000000..dec5de7
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/layout/layout3.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+</LinearLayout>
\ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
index 1ed0c8a..35975ed 100644
--- a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/bools.xml
@@ -9,4 +9,15 @@
     <bool name="bool3">false</bool>
 
     <bool name="bool4" android:featureFlag="test.package.falseFlag">true</bool>
+
+    <bool name="bool5">false</bool>
+    <bool name="bool5" android:featureFlag="!test.package.falseFlag">true</bool>
+
+    <bool name="bool6">true</bool>
+    <bool name="bool6" android:featureFlag="!test.package.trueFlag">false</bool>
+
+    <bool name="bool7">true</bool>
+    <bool name="bool8">false</bool>
+    <bool name="bool9">true</bool>
+    <bool name="bool10">false</bool>
 </resources>
\ No newline at end of file
diff --git "a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050\041test.package.falseFlag\051/bools.xml" "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050\041test.package.falseFlag\051/bools.xml"
new file mode 100644
index 0000000..a63749c
--- /dev/null
+++ "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050\041test.package.falseFlag\051/bools.xml"
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <bool name="bool10">true</bool>
+</resources>
\ No newline at end of file
diff --git "a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050\041test.package.trueFlag\051/bools.xml" "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050\041test.package.trueFlag\051/bools.xml"
new file mode 100644
index 0000000..bb5526e
--- /dev/null
+++ "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050\041test.package.trueFlag\051/bools.xml"
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <bool name="bool9">false</bool>
+</resources>
\ No newline at end of file
diff --git "a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050test.package.trueFlag\051/bools.xml" "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050test.package.trueFlag\051/bools.xml"
new file mode 100644
index 0000000..eba780e
--- /dev/null
+++ "b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/flag\050test.package.trueFlag\051/bools.xml"
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <bool name="bool8">true</bool>
+</resources>
\ No newline at end of file
diff --git a/tools/aapt2/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp
index 3db37c2..6293008 100644
--- a/tools/aapt2/link/FlaggedResources_test.cpp
+++ b/tools/aapt2/link/FlaggedResources_test.cpp
@@ -17,6 +17,7 @@
 #include "LoadedApk.h"
 #include "cmd/Dump.h"
 #include "io/StringStream.h"
+#include "test/Common.h"
 #include "test/Test.h"
 #include "text/Printer.h"
 
@@ -75,6 +76,10 @@
 
   std::string output;
   DumpResourceTableToString(loaded_apk.get(), &output);
+  ASSERT_EQ(output.find("bool4"), std::string::npos);
+  ASSERT_EQ(output.find("str1"), std::string::npos);
+  ASSERT_EQ(output.find("layout2"), std::string::npos);
+  ASSERT_EQ(output.find("removedpng"), std::string::npos);
 }
 
 TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTableChunks) {
@@ -86,6 +91,8 @@
 
   ASSERT_EQ(output.find("bool4"), std::string::npos);
   ASSERT_EQ(output.find("str1"), std::string::npos);
+  ASSERT_EQ(output.find("layout2"), std::string::npos);
+  ASSERT_EQ(output.find("removedpng"), std::string::npos);
 }
 
 TEST_F(FlaggedResourcesTest, DisabledResourcesInRJava) {
@@ -98,4 +105,47 @@
   ASSERT_NE(r_contents.find("public static final int str1"), std::string::npos);
 }
 
+TEST_F(FlaggedResourcesTest, TwoValuesSameDisabledFlag) {
+  test::TestDiagnosticsImpl diag;
+  const std::string compiled_files_dir = GetTestPath("compiled");
+  ASSERT_FALSE(CompileFile(
+      GetTestPath("res/values/values.xml"),
+      R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
+           <bool name="bool1" android:featureFlag="test.package.falseFlag">false</bool>
+           <bool name="bool1" android:featureFlag="test.package.falseFlag">true</bool>
+         </resources>)",
+      compiled_files_dir, &diag,
+      {"--feature-flags", "test.package.falseFlag:ro=false,test.package.trueFlag:ro=true"}));
+  ASSERT_TRUE(diag.GetLog().contains("duplicate value for resource 'bool/bool1'"));
+}
+
+TEST_F(FlaggedResourcesTest, TwoValuesSameDisabledFlagDifferentFiles) {
+  test::TestDiagnosticsImpl diag;
+  const std::string compiled_files_dir = GetTestPath("compiled");
+  ASSERT_TRUE(CompileFile(
+      GetTestPath("res/values/values1.xml"),
+      R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
+           <bool name="bool1" android:featureFlag="test.package.falseFlag">false</bool>
+         </resources>)",
+      compiled_files_dir, &diag,
+      {"--feature-flags", "test.package.falseFlag:ro=false,test.package.trueFlag:ro=true"}));
+  ASSERT_TRUE(CompileFile(
+      GetTestPath("res/values/values2.xml"),
+      R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
+           <bool name="bool1" android:featureFlag="test.package.falseFlag">true</bool>
+         </resources>)",
+      compiled_files_dir, &diag,
+      {"--feature-flags", "test.package.falseFlag:ro=false,test.package.trueFlag:ro=true"}));
+  const std::string out_apk = GetTestPath("out.apk");
+  std::vector<std::string> link_args = {
+      "--manifest",
+      GetDefaultManifest(),
+      "-o",
+      out_apk,
+  };
+
+  ASSERT_FALSE(Link(link_args, compiled_files_dir, &diag));
+  ASSERT_TRUE(diag.GetLog().contains("duplicate value for resource 'bool1'"));
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 37a039e..1bef5f8 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -321,6 +321,30 @@
           }
         }
       }
+
+      // disabled values
+      for (auto& src_config_value : src_entry->flag_disabled_values) {
+        auto dst_config_value = dst_entry->FindOrCreateFlagDisabledValue(
+            src_config_value->value->GetFlag().value(), src_config_value->config,
+            src_config_value->product);
+        if (!dst_config_value->value) {
+          // Resource does not exist, add it now.
+          // Must clone the value since it might be in the values vector as well
+          CloningValueTransformer cloner(&main_table_->string_pool);
+          dst_config_value->value = src_config_value->value->Transform(cloner);
+        } else {
+          error = true;
+          context_->GetDiagnostics()->Error(
+              android::DiagMessage(src_config_value->value->GetSource())
+              << "duplicate value for resource '" << src_entry->name << "' " << "with config '"
+              << src_config_value->config << "' and flag '"
+              << (src_config_value->value->GetFlag()->negated ? "!" : "")
+              << src_config_value->value->GetFlag()->name << "'");
+          context_->GetDiagnostics()->Note(
+              android::DiagMessage(dst_config_value->value->GetSource())
+              << "resource previously defined here");
+        }
+      }
     }
   }
   return !error;
@@ -353,6 +377,8 @@
   file_ref->SetSource(file_desc.source);
   file_ref->type = file_desc.type;
   file_ref->file = file;
+  file_ref->SetFlagStatus(file_desc.flag_status);
+  file_ref->SetFlag(file_desc.flag);
 
   ResourceTablePackage* pkg = table.FindOrCreatePackage(file_desc.name.package);
   pkg->FindOrCreateType(file_desc.name.type)
diff --git a/tools/aapt2/test/Common.cpp b/tools/aapt2/test/Common.cpp
index cdf24534..c7dd4c9 100644
--- a/tools/aapt2/test/Common.cpp
+++ b/tools/aapt2/test/Common.cpp
@@ -21,23 +21,6 @@
 namespace aapt {
 namespace test {
 
-struct TestDiagnosticsImpl : public android::IDiagnostics {
-  void Log(Level level, android::DiagMessageActual& actual_msg) override {
-    switch (level) {
-      case Level::Note:
-        return;
-
-      case Level::Warn:
-        std::cerr << actual_msg.source << ": warn: " << actual_msg.message << "." << std::endl;
-        break;
-
-      case Level::Error:
-        std::cerr << actual_msg.source << ": error: " << actual_msg.message << "." << std::endl;
-        break;
-    }
-  }
-};
-
 android::IDiagnostics* GetDiagnostics() {
   static TestDiagnosticsImpl diag;
   return &diag;
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 0437980..b06c432 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -37,6 +37,32 @@
 namespace aapt {
 namespace test {
 
+struct TestDiagnosticsImpl : public android::IDiagnostics {
+  void Log(Level level, android::DiagMessageActual& actual_msg) override {
+    switch (level) {
+      case Level::Note:
+        return;
+
+      case Level::Warn:
+        std::cerr << actual_msg.source << ": warn: " << actual_msg.message << "." << std::endl;
+        log << actual_msg.source << ": warn: " << actual_msg.message << "." << std::endl;
+        break;
+
+      case Level::Error:
+        std::cerr << actual_msg.source << ": error: " << actual_msg.message << "." << std::endl;
+        log << actual_msg.source << ": error: " << actual_msg.message << "." << std::endl;
+        break;
+    }
+  }
+
+  std::string GetLog() {
+    return log.str();
+  }
+
+ private:
+  std::ostringstream log;
+};
+
 android::IDiagnostics* GetDiagnostics();
 
 inline ResourceName ParseNameOrDie(android::StringPiece str) {
diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index b91abe5..570bcf1 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -91,10 +91,13 @@
 }
 
 bool CommandTestFixture::CompileFile(const std::string& path, const std::string& contents,
-                                     android::StringPiece out_dir, android::IDiagnostics* diag) {
+                                     android::StringPiece out_dir, android::IDiagnostics* diag,
+                                     const std::vector<android::StringPiece>& additional_args) {
   WriteFile(path, contents);
   CHECK(file::mkdirs(out_dir.data()));
-  return CompileCommand(diag).Execute({path, "-o", out_dir, "-v"}, &std::cerr) == 0;
+  std::vector<android::StringPiece> args = {path, "-o", out_dir, "-v"};
+  args.insert(args.end(), additional_args.begin(), additional_args.end());
+  return CompileCommand(diag).Execute(args, &std::cerr) == 0;
 }
 
 bool CommandTestFixture::Link(const std::vector<std::string>& args, android::IDiagnostics* diag) {
diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h
index 14298d16..178d011 100644
--- a/tools/aapt2/test/Fixture.h
+++ b/tools/aapt2/test/Fixture.h
@@ -73,7 +73,8 @@
   // Wries the contents of the file to the specified path. The file is compiled and the flattened
   // file is written to the out directory.
   bool CompileFile(const std::string& path, const std::string& contents,
-                   android::StringPiece flat_out_dir, android::IDiagnostics* diag);
+                   android::StringPiece flat_out_dir, android::IDiagnostics* diag,
+                   const std::vector<android::StringPiece>& additional_args = {});
 
   // Executes the link command with the specified arguments.
   bool Link(const std::vector<std::string>& args, android::IDiagnostics* diag);