Merge "Update the backup and restore to correct namespace" into main
diff --git a/api/Android.bp b/api/Android.bp
index 6a01677..cdc5cd1 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -230,7 +230,7 @@
     cmd: "$(location merge_zips) $(out) $(in)",
     srcs: [
         ":api-stubs-docs-non-updatable{.exportable}",
-        ":all-modules-public-stubs-source",
+        ":all-modules-public-stubs-source-exportable",
     ],
     visibility: ["//visibility:private"], // Used by make module in //development, mind
 }
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index e8fcf4b..1ebe0cd 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -129,7 +129,7 @@
 droidstubs {
     name: "framework-doc-stubs",
     defaults: ["android-non-updatable-doc-stubs-defaults"],
-    srcs: [":all-modules-public-stubs-source"],
+    srcs: [":all-modules-public-stubs-source-exportable"],
     api_levels_module: "api_versions_public",
     aidl: {
         include_dirs: [
diff --git a/api/api.go b/api/api.go
index f32bdc3..5ca24de 100644
--- a/api/api.go
+++ b/api/api.go
@@ -429,8 +429,9 @@
 
 func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) {
 	props := fgProps{}
-	props.Name = proptools.StringPtr("all-modules-public-stubs-source")
-	props.Device_common_srcs = createSrcs(modules, "{.public.stubs.source}")
+	props.Name = proptools.StringPtr("all-modules-public-stubs-source-exportable")
+	transformConfigurableArray(modules, "", ".stubs.source")
+	props.Device_common_srcs = createSrcs(modules, "{.exportable}")
 	props.Visibility = []string{"//frameworks/base"}
 	ctx.CreateModule(android.FileGroupFactory, &props)
 }
diff --git a/cmds/idmap2/libidmap2/ResourceContainer.cpp b/cmds/idmap2/libidmap2/ResourceContainer.cpp
index 3c0e118..57ae354 100644
--- a/cmds/idmap2/libidmap2/ResourceContainer.cpp
+++ b/cmds/idmap2/libidmap2/ResourceContainer.cpp
@@ -17,6 +17,7 @@
 #include "idmap2/ResourceContainer.h"
 
 #include <memory>
+#include <mutex>
 #include <string>
 #include <utility>
 #include <vector>
@@ -296,7 +297,7 @@
 }  // namespace
 
 struct ApkResourceContainer : public TargetResourceContainer, public OverlayResourceContainer {
-  static Result<std::unique_ptr<ApkResourceContainer>> FromPath(const std::string& path);
+  static Result<std::unique_ptr<ApkResourceContainer>> FromPath(std::string path);
 
   // inherited from TargetResourceContainer
   Result<bool> DefinesOverlayable() const override;
@@ -320,6 +321,7 @@
   Result<const ResState*> GetState() const;
   ZipAssetsProvider* GetZipAssets() const;
 
+  mutable std::mutex state_lock_;
   mutable std::variant<std::unique_ptr<ZipAssetsProvider>, ResState> state_;
   std::string path_;
 };
@@ -330,16 +332,17 @@
 }
 
 Result<std::unique_ptr<ApkResourceContainer>> ApkResourceContainer::FromPath(
-    const std::string& path) {
+    std::string path) {
   auto zip_assets = ZipAssetsProvider::Create(path, 0 /* flags */);
   if (zip_assets == nullptr) {
     return Error("failed to load zip assets");
   }
   return std::unique_ptr<ApkResourceContainer>(
-      new ApkResourceContainer(std::move(zip_assets), path));
+      new ApkResourceContainer(std::move(zip_assets), std::move(path)));
 }
 
 Result<const ResState*> ApkResourceContainer::GetState() const {
+  std::lock_guard lock(state_lock_);
   if (auto state = std::get_if<ResState>(&state_); state != nullptr) {
     return state;
   }
@@ -355,6 +358,7 @@
 }
 
 ZipAssetsProvider* ApkResourceContainer::GetZipAssets() const {
+  std::lock_guard lock(state_lock_);
   if (auto zip = std::get_if<std::unique_ptr<ZipAssetsProvider>>(&state_); zip != nullptr) {
     return zip->get();
   }
@@ -427,7 +431,7 @@
 
 Result<std::unique_ptr<TargetResourceContainer>> TargetResourceContainer::FromPath(
     std::string path) {
-  auto result = ApkResourceContainer::FromPath(path);
+  auto result = ApkResourceContainer::FromPath(std::move(path));
   if (!result) {
     return result.GetError();
   }
@@ -438,7 +442,7 @@
     std::string path) {
   // Load the path as a fabricated overlay if the file magic indicates this is a fabricated overlay.
   if (android::IsFabricatedOverlay(path)) {
-    auto result = FabricatedOverlayContainer::FromPath(path);
+    auto result = FabricatedOverlayContainer::FromPath(std::move(path));
     if (!result) {
       return result.GetError();
     }
@@ -446,7 +450,7 @@
   }
 
   // Fallback to loading the container as an APK.
-  auto result = ApkResourceContainer::FromPath(path);
+  auto result = ApkResourceContainer::FromPath(std::move(path));
   if (!result) {
     return result.GetError();
   }
diff --git a/core/api/current.txt b/core/api/current.txt
index f03ef8c..c0b6ab6 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8857,7 +8857,7 @@
     method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR;
-    field public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
+    field public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue";
   }
 
 }
@@ -55634,7 +55634,7 @@
     method public android.os.Bundle getExtras();
     method public CharSequence getHintText();
     method public int getInputType();
-    method public android.view.accessibility.AccessibilityNodeInfo getLabelFor();
+    method @Deprecated @FlaggedApi("android.view.accessibility.deprecate_ani_label_for_apis") public android.view.accessibility.AccessibilityNodeInfo getLabelFor();
     method @Deprecated @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public android.view.accessibility.AccessibilityNodeInfo getLabeledBy();
     method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") @NonNull public java.util.List<android.view.accessibility.AccessibilityNodeInfo> getLabeledByList();
     method public int getLiveRegion();
@@ -55733,8 +55733,8 @@
     method public void setHintText(CharSequence);
     method public void setImportantForAccessibility(boolean);
     method public void setInputType(int);
-    method public void setLabelFor(android.view.View);
-    method public void setLabelFor(android.view.View, int);
+    method @Deprecated @FlaggedApi("android.view.accessibility.deprecate_ani_label_for_apis") public void setLabelFor(android.view.View);
+    method @Deprecated @FlaggedApi("android.view.accessibility.deprecate_ani_label_for_apis") public void setLabelFor(android.view.View, int);
     method @Deprecated @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void setLabeledBy(android.view.View);
     method @Deprecated @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void setLabeledBy(android.view.View, int);
     method public void setLiveRegion(int);
diff --git a/core/java/android/adaptiveauth/OWNERS b/core/java/android/adaptiveauth/OWNERS
index 0218a78..bc8efa9 100644
--- a/core/java/android/adaptiveauth/OWNERS
+++ b/core/java/android/adaptiveauth/OWNERS
@@ -1 +1 @@
-include /services/core/java/com/android/server/adaptiveauth/OWNERS
\ No newline at end of file
+include /services/core/java/com/android/server/security/adaptiveauthentication/OWNERS
\ No newline at end of file
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index f432a22..1dc7742 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -32,7 +32,10 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -92,9 +95,6 @@
          * caching on behalf of other processes.
          */
         public boolean shouldBypassCache(@NonNull Q query) {
-            if(android.multiuser.Flags.propertyInvalidatedCacheBypassMismatchedUids()) {
-                return Binder.getCallingUid() != Process.myUid();
-            }
             return false;
         }
     };
@@ -392,8 +392,213 @@
         }
     }
 
+    /**
+     * An array of hash maps, indexed by calling UID.  The class behaves a bit like a hash map
+     * except that it uses the calling UID internally.
+     */
+    private class CacheMap<Query, Result> {
+
+        // Create a new map for a UID, using the parent's configuration for max size.
+        private LinkedHashMap<Query, Result> createMap() {
+            return new LinkedHashMap<Query, Result>(
+                2 /* start small */,
+                0.75f /* default load factor */,
+                true /* LRU access order */) {
+                @GuardedBy("mLock")
+                @Override
+                protected boolean removeEldestEntry(Map.Entry eldest) {
+                    final int size = size();
+                    if (size > mHighWaterMark) {
+                        mHighWaterMark = size;
+                    }
+                    if (size > mMaxEntries) {
+                        mMissOverflow++;
+                        return true;
+                    }
+                    return false;
+                }
+            };
+        }
+
+        // An array of maps, indexed by UID.
+        private final SparseArray<LinkedHashMap<Query, Result>> mCache = new SparseArray<>();
+
+        // If true, isolate the hash entries by calling UID.  If this is false, allow the cache
+        // entries to be combined in a single hash map.
+        private final boolean mIsolated;
+
+        // Collect statistics.
+        private final boolean mStatistics;
+
+        // An array of booleans to indicate if a UID has been involved in a map access.  A value
+        // exists for every UID that was ever involved during cache access. This is updated only
+        // if statistics are being collected.
+        private final SparseBooleanArray mUidSeen;
+
+        // A hash map that ignores the UID.  This is used in look-aside fashion just for hit/miss
+        // statistics.  This is updated only if statistics are being collected.
+        private final ArraySet<Query> mShadowCache;
+
+        // Shadow statistics.  Only hits and misses need to be recorded.  These are updated only
+        // if statistics are being collected.  The "SelfHits" records hits when the UID is the
+        // process uid.
+        private int mShadowHits;
+        private int mShadowMisses;
+        private int mShadowSelfHits;
+
+        // The process UID.
+        private final int mSelfUid;
+
+        // True in test mode.  In test mode, the cache uses Binder.getWorkSource() as the UID.
+        private final boolean mTestMode;
+
+        /**
+         * Create a CacheMap.  UID isolation is enabled if the input parameter is true and if the
+         * isolation feature is enabled.
+         */
+        CacheMap(boolean isolate, boolean testMode) {
+            mIsolated = Flags.picIsolateCacheByUid() && isolate;
+            mStatistics = Flags.picIsolatedCacheStatistics() && mIsolated;
+            if (mStatistics) {
+                mUidSeen = new SparseBooleanArray();
+                mShadowCache = new ArraySet<>();
+            } else {
+                mUidSeen = null;
+                mShadowCache = null;
+            }
+            mSelfUid = Process.myUid();
+            mTestMode = testMode;
+        }
+
+        // Return the UID for this cache invocation.  If uid isolation is disabled, the value of 0
+        // is returned, which effectively places all entries in a single hash map.
+        private int callerUid() {
+            if (!mIsolated) {
+                return 0;
+            } else if (mTestMode) {
+                return Binder.getCallingWorkSourceUid();
+            } else {
+                return Binder.getCallingUid();
+            }
+        }
+
+        /**
+         * Lookup an entry in the cache.
+         */
+        Result get(Query query) {
+            final int uid = callerUid();
+
+            // Shadow statistics
+            if (mStatistics) {
+                if (mShadowCache.contains(query)) {
+                    mShadowHits++;
+                    if (uid == mSelfUid) {
+                        mShadowSelfHits++;
+                    }
+                } else {
+                    mShadowMisses++;
+                }
+            }
+
+            var map = mCache.get(uid);
+            if (map != null) {
+                return map.get(query);
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Remove an entry from the cache.
+         */
+        void remove(Query query) {
+            final int uid = callerUid();
+            if (mStatistics) {
+                mShadowCache.remove(query);
+            }
+
+            var map = mCache.get(uid);
+            if (map != null) {
+                map.remove(query);
+            }
+        }
+
+        /**
+         * Record an entry in the cache.
+         */
+        void put(Query query, Result result) {
+            final int uid = callerUid();
+            if (mStatistics) {
+                mShadowCache.add(query);
+                mUidSeen.put(uid, true);
+            }
+
+            var map = mCache.get(uid);
+            if (map == null) {
+                map = createMap();
+                mCache.put(uid, map);
+            }
+            map.put(query, result);
+        }
+
+        /**
+         * Return the number of entries in the cache.
+         */
+        int size() {
+            int total = 0;
+            for (int i = 0; i < mCache.size(); i++) {
+                var map = mCache.valueAt(i);
+                total += map.size();
+            }
+            return total;
+        }
+
+        /**
+         * Clear the entries in the cache.  Update the shadow statistics.
+         */
+        void clear() {
+            if (mStatistics) {
+                mShadowCache.clear();
+            }
+
+            mCache.clear();
+        }
+
+        // Dump basic statistics, if any are collected.  Do nothing if statistics are not enabled.
+        void dump(PrintWriter pw) {
+            if (mStatistics) {
+                pw.println(formatSimple("    ShadowHits: %d, ShadowMisses: %d, ShadowSize: %d",
+                                mShadowHits, mShadowMisses, mShadowCache.size()));
+                pw.println(formatSimple("    ShadowUids: %d, SelfUid: %d",
+                                mUidSeen.size(), mShadowSelfHits));
+            }
+        }
+
+        // Dump detailed statistics
+        void dumpDetailed(PrintWriter pw) {
+            for (int i = 0; i < mCache.size(); i++) {
+                int uid = mCache.keyAt(i);
+                var map = mCache.valueAt(i);
+
+                Set<Map.Entry<Query, Result>> cacheEntries = map.entrySet();
+                if (cacheEntries.size() == 0) {
+                    break;
+                }
+
+                pw.println("    Contents:");
+                pw.println(formatSimple("      Uid: %d\n", uid));
+                for (Map.Entry<Query, Result> entry : cacheEntries) {
+                    String key = Objects.toString(entry.getKey());
+                    String value = Objects.toString(entry.getValue());
+
+                    pw.println(formatSimple("      Key: %s\n      Value: %s\n", key, value));
+                }
+            }
+        }
+    }
+
     @GuardedBy("mLock")
-    private final LinkedHashMap<Query, Result> mCache;
+    private final CacheMap<Query, Result> mCache;
 
     /**
      * The nonce handler for this cache.
@@ -895,7 +1100,8 @@
      * is allowed to be null in the record constructor to facility reuse of Args instances.
      * @hide
      */
-    public static record Args(@NonNull String mModule, @Nullable String mApi, int mMaxEntries) {
+    public static record Args(@NonNull String mModule, @Nullable String mApi,
+            int mMaxEntries, boolean mIsolateUids, boolean mTestMode) {
 
         // Validation: the module must be one of the known module strings and the maxEntries must
         // be positive.
@@ -909,15 +1115,28 @@
         // which is not legal, but there is no reasonable default.  Clients must call the api
         // method to set the field properly.
         public Args(@NonNull String module) {
-            this(module, /* api */ null, /* maxEntries */ 32);
+            this(module,
+                    null,       // api
+                    32,         // maxEntries
+                    true,       // isolateUids
+                    false       // testMode
+                 );
         }
 
         public Args api(@NonNull String api) {
-            return new Args(mModule, api, mMaxEntries);
+            return new Args(mModule, api, mMaxEntries, mIsolateUids, mTestMode);
         }
 
         public Args maxEntries(int val) {
-            return new Args(mModule, mApi, val);
+            return new Args(mModule, mApi, val, mIsolateUids, mTestMode);
+        }
+
+        public Args isolateUids(boolean val) {
+            return new Args(mModule, mApi, mMaxEntries, val, mTestMode);
+        }
+
+        public Args testMode(boolean val) {
+            return new Args(mModule, mApi, mMaxEntries, mIsolateUids, val);
         }
     }
 
@@ -936,7 +1155,7 @@
         mCacheName = cacheName;
         mNonce = getNonceHandler(mPropertyName);
         mMaxEntries = args.mMaxEntries;
-        mCache = createMap();
+        mCache = new CacheMap<>(args.mIsolateUids, args.mTestMode);
         mComputer = (computer != null) ? computer : new DefaultComputer<>(this);
         registerCache();
     }
@@ -1006,28 +1225,6 @@
         this(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer);
     }
 
-    // Create a map.  This should be called only from the constructor.
-    private LinkedHashMap<Query, Result> createMap() {
-        return new LinkedHashMap<Query, Result>(
-            2 /* start small */,
-            0.75f /* default load factor */,
-            true /* LRU access order */) {
-                @GuardedBy("mLock")
-                @Override
-                protected boolean removeEldestEntry(Map.Entry eldest) {
-                    final int size = size();
-                    if (size > mHighWaterMark) {
-                        mHighWaterMark = size;
-                    }
-                    if (size > mMaxEntries) {
-                        mMissOverflow++;
-                        return true;
-                    }
-                    return false;
-                }
-        };
-    }
-
     /**
      * Register the map in the global list.  If the cache is disabled globally, disable it
      * now.  This method is only ever called from the constructor, which means no other thread has
@@ -1778,8 +1975,8 @@
             pw.println(formatSimple("  Cache Name: %s", cacheName()));
             pw.println(formatSimple("    Property: %s", mPropertyName));
             pw.println(formatSimple(
-                "    Hits: %d, Misses: %d, Skips: %d, Clears: %d",
-                mHits, mMisses, getSkipsLocked(), mClears));
+                "    Hits: %d, Misses: %d, Skips: %d, Clears: %d, Uids: %d",
+                mHits, mMisses, getSkipsLocked(), mClears, mCache.size()));
 
             // Print all the skip reasons.
             pw.format("    Skip-%s: %d", sNonceName[0], mSkips[0]);
@@ -1794,25 +1991,16 @@
             pw.println(formatSimple(
                 "    Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d",
                 mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow));
+            mCache.dump(pw);
             pw.println(formatSimple("    Enabled: %s", mDisabled ? "false" : "true"));
 
-            // No specific cache was requested.  This is the default, and no details
-            // should be dumped.
-            if (!detailed) {
-                return;
-            }
-            Set<Map.Entry<Query, Result>> cacheEntries = mCache.entrySet();
-            if (cacheEntries.size() == 0) {
-                return;
+            // Dump the contents of the cache.
+            if (detailed) {
+                mCache.dumpDetailed(pw);
             }
 
-            pw.println("    Contents:");
-            for (Map.Entry<Query, Result> entry : cacheEntries) {
-                String key = Objects.toString(entry.getKey());
-                String value = Objects.toString(entry.getValue());
-
-                pw.println(formatSimple("      Key: %s\n      Value: %s\n", key, value));
-            }
+            // Separator between caches.
+            pw.println("");
         }
     }
 
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index fee071b..be24bfa 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -367,3 +367,10 @@
     description: "Allows DPMS to enable or disable SupervisionService based on whether the device is being managed by the supervision role holder."
     bug: "376213673"
 }
+
+flag {
+  name: "split_create_managed_profile_enabled"
+  namespace: "enterprise"
+  description: "Split up existing create and provision managed profile API."
+  bug: "375382324"
+}
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index f703026..acad43b 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -20,7 +20,6 @@
 
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.appsearch.GenericDocument;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -65,7 +64,7 @@
      *
      * <p>See {@link #getResultDocument} for more information on extracting the return value.
      */
-    public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
+    public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue";
 
     /**
      * Returns the return value of the executed function.
diff --git a/core/java/android/app/performance.aconfig b/core/java/android/app/performance.aconfig
index f51f748..61b53f9 100644
--- a/core/java/android/app/performance.aconfig
+++ b/core/java/android/app/performance.aconfig
@@ -18,3 +18,20 @@
      description: "Enforce PropertyInvalidatedCache.setTestMode() protocol"
      bug: "360897450"
 }
+
+flag {
+     namespace: "system_performance"
+     name: "pic_isolate_cache_by_uid"
+     is_fixed_read_only: true
+     description: "Ensure that different UIDs use different caches"
+     bug: "373752556"
+}
+
+flag {
+     namespace: "system_performance"
+     name: "pic_isolated_cache_statistics"
+     is_fixed_read_only: true
+     description: "Collects statistics for cache UID isolation strategies"
+     bug: "373752556"
+}
+
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index cc57dc0..ff0bb25 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -398,7 +398,6 @@
          * Retrieve the raw Intent contained in this Item.
          */
         public Intent getIntent() {
-            Intent.maybeMarkAsMissingCreatorToken(mIntent);
             return mIntent;
         }
 
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c054b79..6fa5a9b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -87,7 +87,6 @@
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.XmlUtils;
 import com.android.modules.expresslog.Counter;
 
@@ -109,7 +108,6 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.TimeZone;
-import java.util.function.Consumer;
 
 /**
  * An intent is an abstract description of an operation to be performed.  It
@@ -894,20 +892,6 @@
     public static void maybeMarkAsMissingCreatorToken(Object object) {
         if (object instanceof Intent intent) {
             maybeMarkAsMissingCreatorTokenInternal(intent);
-        } else if (object instanceof Parcelable[] parcelables) {
-            for (Parcelable p : parcelables) {
-                if (p instanceof Intent intent) {
-                    maybeMarkAsMissingCreatorTokenInternal(intent);
-                }
-            }
-        } else if (object instanceof ArrayList parcelables) {
-            int N = parcelables.size();
-            for (int i = 0; i < N; i++) {
-                Object p = parcelables.get(i);
-                if (p instanceof Intent intent) {
-                    maybeMarkAsMissingCreatorTokenInternal(intent);
-                }
-            }
         }
     }
 
@@ -12220,70 +12204,7 @@
         // 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<NestedIntentKey> mNestedIntentKeys;
-    }
-
-    /**
-     * @hide
-     */
-    public static class NestedIntentKey {
-        /** @hide */
-        @IntDef(flag = true, prefix = {"NESTED_INTENT_KEY_TYPE"}, value = {
-                NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL,
-                NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY,
-                NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST,
-                NESTED_INTENT_KEY_TYPE_CLIP_DATA,
-        })
-        @Retention(RetentionPolicy.SOURCE)
-        private @interface NestedIntentKeyType {
-        }
-
-        /**
-         * This flag indicates the key is for an extra parcel in mExtras.
-         */
-        private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL = 1 << 0;
-
-        /**
-         * This flag indicates the key is for an extra parcel array in mExtras and the index is the
-         * index of that array.
-         */
-        private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY = 1 << 1;
-
-        /**
-         * This flag indicates the key is for an extra parcel list in mExtras and the index is the
-         * index of that list.
-         */
-        private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST = 1 << 2;
-
-        /**
-         * This flag indicates the key is for an extra parcel in mClipData.mItems.
-         */
-        private static final int NESTED_INTENT_KEY_TYPE_CLIP_DATA = 1 << 3;
-
-        // type can be a short or even byte. But then probably cannot use @IntDef?? Also not sure
-        // if it is necessary.
-        private final @NestedIntentKeyType int mType;
-        private final String mKey;
-        private final int mIndex;
-
-        private NestedIntentKey(@NestedIntentKeyType int type, String key, int index) {
-            this.mType = type;
-            this.mKey = key;
-            this.mIndex = index;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            NestedIntentKey that = (NestedIntentKey) o;
-            return mType == that.mType && mIndex == that.mIndex && Objects.equals(mKey, that.mKey);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(mType, mKey, mIndex);
-        }
+        private ArraySet<String> mExtraIntentKeys;
     }
 
     private @Nullable CreatorTokenInfo mCreatorTokenInfo;
@@ -12306,9 +12227,8 @@
     }
 
     /** @hide */
-    @VisibleForTesting
-    public Set<NestedIntentKey> getExtraIntentKeys() {
-        return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mNestedIntentKeys;
+    public Set<String> getExtraIntentKeys() {
+        return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mExtraIntentKeys;
     }
 
     /** @hide */
@@ -12326,168 +12246,45 @@
      * @hide
      */
     public void collectExtraIntentKeys() {
-        if (preventIntentRedirect()) {
-            collectNestedIntentKeysRecur(new ArraySet<>());
-        }
-    }
+        if (!preventIntentRedirect()) return;
 
-    private void collectNestedIntentKeysRecur(Set<Intent> visited) {
-        if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) {
+        if (mExtras != null && !mExtras.isEmpty()) {
             for (String key : mExtras.keySet()) {
-                Object value = mExtras.get(key);
-
-                if (value instanceof Intent intent && !visited.contains(intent)) {
-                    handleNestedIntent(intent, visited, new NestedIntentKey(
-                            NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL, key, 0));
-                } else if (value instanceof Parcelable[] parcelables) {
-                    handleParcelableArray(parcelables, key, visited);
-                } else if (value instanceof ArrayList<?> parcelables) {
-                    handleParcelableList(parcelables, key, visited);
-                }
-            }
-        }
-
-        if (mClipData != null) {
-            for (int i = 0; i < mClipData.getItemCount(); i++) {
-                Intent intent = mClipData.getItemAt(i).mIntent;
-                if (intent != null && !visited.contains(intent)) {
-                    handleNestedIntent(intent, visited, new NestedIntentKey(
-                            NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA, null, i));
+                if (mExtras.get(key) instanceof Intent) {
+                    if (mCreatorTokenInfo == null) {
+                        mCreatorTokenInfo = new CreatorTokenInfo();
+                    }
+                    if (mCreatorTokenInfo.mExtraIntentKeys == null) {
+                        mCreatorTokenInfo.mExtraIntentKeys = new ArraySet<>();
+                    }
+                    mCreatorTokenInfo.mExtraIntentKeys.add(key);
                 }
             }
         }
     }
 
-    private void handleNestedIntent(Intent intent, Set<Intent> visited, NestedIntentKey key) {
-        visited.add(intent);
-        if (mCreatorTokenInfo == null) {
-            mCreatorTokenInfo = new CreatorTokenInfo();
-        }
-        if (mCreatorTokenInfo.mNestedIntentKeys == null) {
-            mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>();
-        }
-        mCreatorTokenInfo.mNestedIntentKeys.add(key);
-        intent.collectNestedIntentKeysRecur(visited);
-    }
-
-    private void handleParcelableArray(Parcelable[] parcelables, String key, Set<Intent> visited) {
-        for (int i = 0; i < parcelables.length; i++) {
-            if (parcelables[i] instanceof Intent intent && !visited.contains(intent)) {
-                handleNestedIntent(intent, visited, new NestedIntentKey(
-                        NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY, key, i));
-            }
-        }
-    }
-
-    private void handleParcelableList(ArrayList<?> parcelables, String key, Set<Intent> visited) {
-        for (int i = 0; i < parcelables.size(); i++) {
-            if (parcelables.get(i) instanceof Intent intent && !visited.contains(intent)) {
-                handleNestedIntent(intent, visited, new NestedIntentKey(
-                        NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST, key, i));
-            }
-        }
-    }
-
-    private static final Consumer<Intent> CHECK_CREATOR_TOKEN_ACTION = intent -> {
-        intent.mLocalFlags |= LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT;
-        if (intent.mExtras != null) {
-            intent.mExtras.enableTokenVerification();
-        }
-    };
-
     /** @hide */
     public void checkCreatorToken() {
-        forEachNestedCreatorToken(CHECK_CREATOR_TOKEN_ACTION);
-
-        if (mExtras != null) {
-            // mark the bundle as intent extras after calls to getParcelable.
-            // otherwise, the logic to mark missing token would run before
-            // mark trusted creator token present.
-            mExtras.enableTokenVerification();
-        }
-    }
-
-    /** @hide */
-    public void forEachNestedCreatorToken(Consumer<? super Intent> action) {
-        if (mExtras == null && mClipData == null) return;
-
-        if (mCreatorTokenInfo != null && mCreatorTokenInfo.mNestedIntentKeys != null) {
-            int N = mCreatorTokenInfo.mNestedIntentKeys.size();
-            for (int i = 0; i < N; i++) {
-                NestedIntentKey key = mCreatorTokenInfo.mNestedIntentKeys.valueAt(i);
-                Intent extraIntent = extractIntentFromKey(key);
-
-                if (extraIntent != null) {
-                    action.accept(extraIntent);
-                    extraIntent.forEachNestedCreatorToken(action);
-                } else {
-                    Log.w(TAG, getLogMessageForKey(key));
+        if (mExtras == null) return;
+        if (mCreatorTokenInfo != null && mCreatorTokenInfo.mExtraIntentKeys != null) {
+            for (String key : mCreatorTokenInfo.mExtraIntentKeys) {
+                try {
+                    Intent extraIntent = mExtras.getParcelable(key, Intent.class);
+                    if (extraIntent == null) {
+                        Log.w(TAG, "The key {" + key
+                                + "} does not correspond to an intent in the bundle.");
+                        continue;
+                    }
+                    extraIntent.mLocalFlags |= LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT;
+                } catch (Exception e) {
+                    Log.e(TAG, "Failed to validate creator token. key: " + key + ".", e);
                 }
             }
         }
-    }
-
-    private Intent extractIntentFromKey(NestedIntentKey key) {
-        switch (key.mType) {
-            case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL:
-                return mExtras == null ? null : mExtras.getParcelable(key.mKey, Intent.class);
-            case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY:
-                if (mExtras == null) return null;
-                Intent[] extraIntents = mExtras.getParcelableArray(key.mKey, Intent.class);
-                if (extraIntents != null && key.mIndex < extraIntents.length) {
-                    return extraIntents[key.mIndex];
-                }
-                break;
-            case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST:
-                if (mExtras == null) return null;
-                ArrayList<Intent> extraIntentsList = mExtras.getParcelableArrayList(key.mKey,
-                        Intent.class);
-                if (extraIntentsList != null && key.mIndex < extraIntentsList.size()) {
-                    return extraIntentsList.get(key.mIndex);
-                }
-                break;
-            case NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA:
-                if (mClipData == null) return null;
-                if (key.mIndex < mClipData.getItemCount()) {
-                    ClipData.Item item = mClipData.getItemAt(key.mIndex);
-                    if (item != null) {
-                        return item.mIntent;
-                    }
-                }
-                break;
-        }
-        return null;
-    }
-
-    private String getLogMessageForKey(NestedIntentKey key) {
-        switch (key.mType) {
-            case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL:
-                return "The key {" + key + "} does not correspond to an intent in the bundle.";
-            case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY:
-                if (mExtras.getParcelableArray(key.mKey, Intent.class) == null) {
-                    return "The key {" + key
-                            + "} does not correspond to a Parcelable[] in the bundle.";
-                } else {
-                    return "Parcelable[" + key.mIndex + "] for key {" + key + "} is not an intent.";
-                }
-            case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST:
-                if (mExtras.getParcelableArrayList(key.mKey, Intent.class) == null) {
-                    return "The key {" + key
-                            + "} does not correspond to an ArrayList<Parcelable> in the bundle.";
-                } else {
-                    return "List.get(" + key.mIndex + ") for key {" + key + "} is not an intent.";
-                }
-            case NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA:
-                if (key.mIndex >= mClipData.getItemCount()) {
-                    return "Index out of range for clipData items. index: " + key.mIndex
-                            + ". item counts: " + mClipData.getItemCount();
-                } else {
-                    return "clipData items at index [" + key.mIndex
-                            + "] is null or does not contain an intent.";
-                }
-            default:
-                return "Unknown key type: " + key.mType;
-        }
+        // mark the bundle as intent extras after calls to getParcelable.
+        // otherwise, the logic to mark missing token would run before
+        // mark trusted creator token present.
+        mExtras.setIsIntentExtra();
     }
 
     /**
@@ -12560,19 +12357,7 @@
             } else {
                 out.writeInt(1);
                 out.writeStrongBinder(mCreatorTokenInfo.mCreatorToken);
-
-                if (mCreatorTokenInfo.mNestedIntentKeys != null) {
-                    final int N = mCreatorTokenInfo.mNestedIntentKeys.size();
-                    out.writeInt(N);
-                    for (int i = 0; i < N; i++) {
-                        NestedIntentKey key = mCreatorTokenInfo.mNestedIntentKeys.valueAt(i);
-                        out.writeInt(key.mType);
-                        out.writeString8(key.mKey);
-                        out.writeInt(key.mIndex);
-                    }
-                } else {
-                    out.writeInt(0);
-                }
+                out.writeArraySet(mCreatorTokenInfo.mExtraIntentKeys);
             }
         }
     }
@@ -12637,18 +12422,7 @@
             if (in.readInt() != 0) {
                 mCreatorTokenInfo = new CreatorTokenInfo();
                 mCreatorTokenInfo.mCreatorToken = in.readStrongBinder();
-
-                N = in.readInt();
-                if (N > 0) {
-                    mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>(N);
-                    for (int i = 0; i < N; i++) {
-                        int type = in.readInt();
-                        String key = in.readString8();
-                        int index = in.readInt();
-                        mCreatorTokenInfo.mNestedIntentKeys.append(
-                                new NestedIntentKey(type, key, index));
-                    }
-                }
+                mCreatorTokenInfo.mExtraIntentKeys = (ArraySet<String>) in.readArraySet(null);
             }
         }
     }
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 9d42b67..506a19c 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -117,6 +117,8 @@
     public static final int KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW = 69;
     public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 70;
     public static final int KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE = 71;
+    public static final int KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN = 72;
+    public static final int KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT = 73;
 
     public static final int FLAG_CANCELLED = 1;
 
@@ -203,6 +205,8 @@
             KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
             KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
             KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
+            KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN,
+            KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface KeyGestureType {
@@ -773,6 +777,10 @@
                 return "KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW";
             case KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE:
                 return "KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE";
+            case KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN:
+                return "KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN";
+            case KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT:
+                return "KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT";
             default:
                 return Integer.toHexString(value);
         }
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 99e7d166..c18fb0c 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -281,7 +281,7 @@
     }
 
     /** {@hide} */
-    public void enableTokenVerification() {
+    public void setIsIntentExtra() {
         mFlags |= FLAG_VERIFY_TOKENS_PRESENT;
     }
 
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 1d35344..7cb0ffc 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -120,3 +120,10 @@
     description: "Feature flag for exposing KeyStore grant APIs"
     bug: "351158708"
 }
+
+flag {
+    name: "secure_lockdown"
+    namespace: "biometrics"
+    description: "Feature flag for Secure Lockdown feature"
+    bug: "373422357"
+}
\ No newline at end of file
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index bce51f2..1df3b43 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -37,6 +37,7 @@
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.MediaQualityStatus;
+import android.telephony.satellite.NtnSignalStrength;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IPhoneStateListener;
@@ -1706,6 +1707,11 @@
                 @NetworkRegistrationInfo.ServiceType int[] availableServices) {
             // not supported on the deprecated interface - Use TelephonyCallback instead
         }
+
+        public final void onCarrierRoamingNtnSignalStrengthChanged(
+                @NonNull NtnSignalStrength ntnSignalStrength) {
+            // not supported on the deprecated interface - Use TelephonyCallback instead
+        }
     }
 
     private void log(String s) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 64a5533..0d1dc46 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -30,6 +30,7 @@
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.MediaQualityStatus;
 import android.telephony.ims.MediaThreshold;
+import android.telephony.satellite.NtnSignalStrength;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -695,6 +696,15 @@
     public static final int EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED = 44;
 
     /**
+     * Event for listening to carrier roaming non-terrestrial network signal strength changes.
+     *
+     * @see CarrierRoamingNtnModeListener
+     *
+     * @hide
+     */
+    public static final int EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED = 45;
+
+    /**
      * @hide
      */
     @IntDef(prefix = {"EVENT_"}, value = {
@@ -741,7 +751,8 @@
             EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED,
             EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED,
             EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED,
-            EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED
+            EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED,
+            EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface TelephonyEvent {
@@ -1805,6 +1816,14 @@
          */
         default void onCarrierRoamingNtnAvailableServicesChanged(
                 @NetworkRegistrationInfo.ServiceType List<Integer> availableServices) {}
+
+        /**
+         * Callback invoked when carrier roaming non-terrestrial network signal strength changes.
+         *
+         * @param ntnSignalStrength non-terrestrial network signal strength.
+         */
+        default void onCarrierRoamingNtnSignalStrengthChanged(
+                @NonNull NtnSignalStrength ntnSignalStrength) {}
     }
 
     /**
@@ -2270,5 +2289,18 @@
             Binder.withCleanCallingIdentity(() -> mExecutor.execute(
                     () -> listener.onCarrierRoamingNtnAvailableServicesChanged(ServiceList)));
         }
+
+        public void onCarrierRoamingNtnSignalStrengthChanged(
+                @NonNull NtnSignalStrength ntnSignalStrength) {
+            if (!Flags.carrierRoamingNbIotNtn()) return;
+
+            CarrierRoamingNtnModeListener listener =
+                    (CarrierRoamingNtnModeListener) mTelephonyCallbackWeakRef.get();
+            if (listener == null) return;
+
+            Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+                    () -> listener.onCarrierRoamingNtnSignalStrengthChanged(ntnSignalStrength)));
+
+        }
     }
 }
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 1dab2cf..90b0bb3 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -47,6 +47,7 @@
 import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.MediaQualityStatus;
+import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.SatelliteStateChangeListener;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -1137,6 +1138,23 @@
     }
 
     /**
+     * Notify external listeners that carrier roaming non-terrestrial network
+     * signal strength changed.
+     * @param subId subscription ID.
+     * @param ntnSignalStrength non-terrestrial network signal strength.
+     * @hide
+     */
+    public final void notifyCarrierRoamingNtnSignalStrengthChanged(int subId,
+            @NonNull NtnSignalStrength ntnSignalStrength) {
+        try {
+            sRegistry.notifyCarrierRoamingNtnSignalStrengthChanged(subId, ntnSignalStrength);
+        } catch (RemoteException ex) {
+            // system server crash
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Processes potential event changes from the provided {@link TelephonyCallback}.
      *
      * @param telephonyCallback callback for monitoring callback changes to the telephony state.
@@ -1293,6 +1311,7 @@
             eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED);
             eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED);
             eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED);
+            eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED);
         }
         return eventList;
     }
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 14652035..0204517 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -3866,8 +3866,14 @@
      * Sets the view for which the view represented by this info serves as a
      * label for accessibility purposes.
      *
+     * @deprecated Use {@link #addLabeledBy(View)} on the labeled node instead,
+     * since {@link #getLabeledByList()} and {@link #getLabeledBy()} on the
+     * labeled node are not automatically populated when this method is used.
+     *
      * @param labeled The view for which this info serves as a label.
      */
+    @FlaggedApi(Flags.FLAG_DEPRECATE_ANI_LABEL_FOR_APIS)
+    @Deprecated
     public void setLabelFor(View labeled) {
         setLabelFor(labeled, AccessibilityNodeProvider.HOST_VIEW_ID);
     }
@@ -3888,9 +3894,15 @@
      *   This class is made immutable before being delivered to an AccessibilityService.
      * </p>
      *
+     * @deprecated Use {@link #addLabeledBy(View)} on the labeled node instead,
+     * since {@link #getLabeledByList()} and {@link #getLabeledBy()} on the
+     * labeled node are not automatically populated when this method is used.
+     *
      * @param root The root whose virtual descendant serves as a label.
      * @param virtualDescendantId The id of the virtual descendant.
      */
+    @FlaggedApi(Flags.FLAG_DEPRECATE_ANI_LABEL_FOR_APIS)
+    @Deprecated
     public void setLabelFor(View root, int virtualDescendantId) {
         enforceNotSealed();
         final int rootAccessibilityViewId = (root != null)
@@ -3902,8 +3914,14 @@
      * Gets the node info for which the view represented by this info serves as
      * a label for accessibility purposes.
      *
+     * @deprecated Use {@link #getLabeledByList()} on the labeled node instead,
+     * since calling {@link #addLabeledBy(View)} or {@link #addLabeledBy(View, int)}
+     * on the labeled node do not automatically provide that node from this method.
+     *
      * @return The labeled info.
      */
+    @FlaggedApi(Flags.FLAG_DEPRECATE_ANI_LABEL_FOR_APIS)
+    @Deprecated
     public AccessibilityNodeInfo getLabelFor() {
         enforceSealed();
         return getNodeForAccessibilityId(mConnectionId, mWindowId, mLabelForId);
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 7177ef3..8a006fa 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -92,6 +92,13 @@
 
 flag {
     namespace: "accessibility"
+    name: "deprecate_ani_label_for_apis"
+    description: "Controls the deprecation of AccessibilityNodeInfo labelFor apis"
+    bug: "333783827"
+}
+
+flag {
+    namespace: "accessibility"
     name: "fix_merged_content_change_event_v2"
     description: "Fixes event type and source of content change event merged in ViewRootImpl"
     bug: "277305460"
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index aa4927e..edd9d6c 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -158,3 +158,11 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+    name: "writing_tools"
+    namespace: "input_method"
+    description: "Writing tools API"
+    bug: "373788889"
+    is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index b5c87868..0e85e04 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -27,6 +27,7 @@
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.PreciseCallState;
 import android.telephony.PreciseDataConnectionState;
+import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.emergency.EmergencyNumber;
@@ -85,4 +86,5 @@
     void onCarrierRoamingNtnModeChanged(in boolean active);
     void onCarrierRoamingNtnEligibleStateChanged(in boolean eligible);
     void onCarrierRoamingNtnAvailableServicesChanged(in int[] availableServices);
+    void onCarrierRoamingNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength);
 }
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 1c76a6c..0f268d5d 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -29,6 +29,7 @@
 import android.telephony.PhoneCapability;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.PreciseDataConnectionState;
+import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.emergency.EmergencyNumber;
@@ -125,8 +126,10 @@
     void notifyCarrierRoamingNtnModeChanged(int subId, in boolean active);
     void notifyCarrierRoamingNtnEligibleStateChanged(int subId, in boolean eligible);
     void notifyCarrierRoamingNtnAvailableServicesChanged(int subId, in int[] availableServices);
+    void notifyCarrierRoamingNtnSignalStrengthChanged(int subId, in NtnSignalStrength ntnSignalStrength);
 
     void addSatelliteStateChangeListener(ISatelliteStateChangeListener listener, String pkg, String featureId);
     void removeSatelliteStateChangeListener(ISatelliteStateChangeListener listener, String pkg);
     void notifySatelliteStateChanged(boolean isEnabled);
+
 }
diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
index 30deb49..fb6937c 100644
--- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java
+++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
@@ -63,6 +63,7 @@
 
     private final ArrayList<Part> mParts = new ArrayList<>();
 
+    private final RectF mSegRectF = new RectF();
     private final Rect mPointRect = new Rect();
     private final RectF mPointRectF = new RectF();
 
@@ -198,22 +199,42 @@
                         mState.mSegSegGap, x + segWidth, totalWidth);
                 final float end = x + segWidth - endOffset;
 
-                // Transparent is not allowed (and also is the default in the data), so use that
-                // as a sentinel to be replaced by default
-                mStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
-                        : mState.mStrokeColor);
-                mDashedStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
-                        : mState.mFadedStrokeColor);
-
-                // Leave space for the rounded line cap which extends beyond start/end.
-                final float capWidth = mStrokePaint.getStrokeWidth() / 2F;
-
-                canvas.drawLine(start + capWidth, centerY, end - capWidth, centerY,
-                        segment.mDashed ? mDashedStrokePaint : mStrokePaint);
-
                 // Advance the current position to account for the segment's fraction of the total
                 // width (ignoring offset and padding)
                 x += segWidth;
+
+                // No space left to draw the segment
+                if (start > end) continue;
+
+                if (segment.mDashed) {
+                    // No caps when the segment is dashed.
+
+                    mDashedStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
+                            : mState.mFadedStrokeColor);
+                    canvas.drawLine(start, centerY, end, centerY, mDashedStrokePaint);
+                } else if (end - start < mState.mStrokeWidth) {
+                    // Not enough segment length to draw the caps
+
+                    final float rad = (end - start) / 2F;
+                    final float capWidth = mStrokePaint.getStrokeWidth() / 2F;
+
+                    mFillPaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
+                            : mState.mStrokeColor);
+
+                    mSegRectF.set(start, centerY - capWidth, end, centerY + capWidth);
+                    canvas.drawRoundRect(mSegRectF, rad, rad, mFillPaint);
+                } else {
+                    // Leave space for the rounded line cap which extends beyond start/end.
+                    final float capWidth = mStrokePaint.getStrokeWidth() / 2F;
+
+                    // Transparent is not allowed (and also is the default in the data), so use that
+                    // as a sentinel to be replaced by default
+                    mStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
+                            : mState.mStrokeColor);
+
+                    canvas.drawLine(start + capWidth, centerY, end - capWidth, centerY,
+                            mStrokePaint);
+                }
             } else if (part instanceof Point point) {
                 final float pointWidth = 2 * pointRadius;
                 float start = x - pointRadius;
@@ -232,7 +253,7 @@
                 } else {
                     // TODO: b/367804171 - actually use a vector asset for the default point
                     //  rather than drawing it as a box?
-                    mPointRectF.set(mPointRect);
+                    mPointRectF.set(start, centerY - pointRadius, end, centerY + pointRadius);
                     final float inset = mState.mPointRectInset;
                     final float cornerRadius = mState.mPointRectCornerRadius;
                     mPointRectF.inset(inset, inset);
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index 69f6334..f1c4913 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -83,68 +83,53 @@
     jmethodID ctor;
 } gRegionClassInfo;
 
-static Mutex gHandleMutex;
+// --- Global functions ---
 
+sp<gui::WindowInfoHandle> android_view_InputWindowHandle_getHandle(JNIEnv* env, jobject obj) {
+    sp<gui::WindowInfoHandle> handle = [&]() {
+        jlong cachedHandle = env->GetLongField(obj, gInputWindowHandleClassInfo.ptr);
+        if (cachedHandle) {
+            return sp<gui::WindowInfoHandle>::fromExisting(
+                    reinterpret_cast<gui::WindowInfoHandle*>(cachedHandle));
+        }
 
-// --- NativeInputWindowHandle ---
+        auto newHandle = sp<gui::WindowInfoHandle>::make();
+        newHandle->incStrong((void*)android_view_InputWindowHandle_getHandle);
+        env->SetLongField(obj, gInputWindowHandleClassInfo.ptr,
+                          reinterpret_cast<jlong>(newHandle.get()));
+        return newHandle;
+    }();
 
-NativeInputWindowHandle::NativeInputWindowHandle(jweak objWeak) :
-        mObjWeak(objWeak) {
-}
+    gui::WindowInfo* windowInfo = handle->editInfo();
 
-NativeInputWindowHandle::~NativeInputWindowHandle() {
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
-    env->DeleteWeakGlobalRef(mObjWeak);
-
-    // Clear the weak reference to the layer handle and flush any binder ref count operations so we
-    // do not hold on to any binder references.
-    // TODO(b/139697085) remove this after it can be flushed automatically
-    mInfo.touchableRegionCropHandle.clear();
-    IPCThreadState::self()->flushCommands();
-}
-
-jobject NativeInputWindowHandle::getInputWindowHandleObjLocalRef(JNIEnv* env) {
-    return env->NewLocalRef(mObjWeak);
-}
-
-bool NativeInputWindowHandle::updateInfo() {
-    JNIEnv* env = AndroidRuntime::getJNIEnv();
-    jobject obj = env->NewLocalRef(mObjWeak);
-    if (!obj) {
-        releaseChannel();
-        return false;
-    }
-
-    mInfo.touchableRegion.clear();
+    windowInfo->touchableRegion.clear();
 
     jobject tokenObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.token);
     if (tokenObj) {
-        mInfo.token = ibinderForJavaObject(env, tokenObj);
+        windowInfo->token = ibinderForJavaObject(env, tokenObj);
         env->DeleteLocalRef(tokenObj);
     } else {
-        mInfo.token.clear();
+        windowInfo->token.clear();
     }
 
-    mInfo.name = getStringField(env, obj, gInputWindowHandleClassInfo.name, "<null>");
+    windowInfo->name = getStringField(env, obj, gInputWindowHandleClassInfo.name, "<null>");
 
-    mInfo.dispatchingTimeout = std::chrono::milliseconds(
+    windowInfo->dispatchingTimeout = std::chrono::milliseconds(
             env->GetLongField(obj, gInputWindowHandleClassInfo.dispatchingTimeoutMillis));
 
     ScopedLocalRef<jobject> frameObj(env,
                                      env->GetObjectField(obj, gInputWindowHandleClassInfo.frame));
-    mInfo.frame = JNICommon::rectFromObj(env, frameObj.get());
+    windowInfo->frame = JNICommon::rectFromObj(env, frameObj.get());
 
-    mInfo.surfaceInset = env->GetIntField(obj,
-            gInputWindowHandleClassInfo.surfaceInset);
-    mInfo.globalScaleFactor = env->GetFloatField(obj,
-            gInputWindowHandleClassInfo.scaleFactor);
+    windowInfo->surfaceInset = env->GetIntField(obj, gInputWindowHandleClassInfo.surfaceInset);
+    windowInfo->globalScaleFactor =
+            env->GetFloatField(obj, gInputWindowHandleClassInfo.scaleFactor);
 
-    jobject regionObj = env->GetObjectField(obj,
-            gInputWindowHandleClassInfo.touchableRegion);
+    jobject regionObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.touchableRegion);
     if (regionObj) {
         for (graphics::RegionIterator it(env, regionObj); !it.isDone(); it.next()) {
             ARect rect = it.getRect();
-            mInfo.addTouchableRegion(Rect(rect.left, rect.top, rect.right, rect.bottom));
+            windowInfo->addTouchableRegion(Rect(rect.left, rect.top, rect.right, rect.bottom));
         }
         env->DeleteLocalRef(regionObj);
     }
@@ -153,49 +138,55 @@
             env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsFlags));
     const auto type = static_cast<WindowInfo::Type>(
             env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsType));
-    mInfo.layoutParamsFlags = flags;
-    mInfo.layoutParamsType = type;
+    windowInfo->layoutParamsFlags = flags;
+    windowInfo->layoutParamsType = type;
 
-    mInfo.inputConfig = static_cast<gui::WindowInfo::InputConfig>(
+    windowInfo->inputConfig = static_cast<gui::WindowInfo::InputConfig>(
             env->GetIntField(obj, gInputWindowHandleClassInfo.inputConfig));
 
-    mInfo.touchOcclusionMode = static_cast<TouchOcclusionMode>(
+    windowInfo->touchOcclusionMode = static_cast<TouchOcclusionMode>(
             env->GetIntField(obj, gInputWindowHandleClassInfo.touchOcclusionMode));
-    mInfo.ownerPid = gui::Pid{env->GetIntField(obj, gInputWindowHandleClassInfo.ownerPid)};
-    mInfo.ownerUid = gui::Uid{
+    windowInfo->ownerPid = gui::Pid{env->GetIntField(obj, gInputWindowHandleClassInfo.ownerPid)};
+    windowInfo->ownerUid = gui::Uid{
             static_cast<uid_t>(env->GetIntField(obj, gInputWindowHandleClassInfo.ownerUid))};
-    mInfo.packageName = getStringField(env, obj, gInputWindowHandleClassInfo.packageName, "<null>");
-    mInfo.displayId =
+    windowInfo->packageName =
+            getStringField(env, obj, gInputWindowHandleClassInfo.packageName, "<null>");
+    windowInfo->displayId =
             ui::LogicalDisplayId{env->GetIntField(obj, gInputWindowHandleClassInfo.displayId)};
 
-    jobject inputApplicationHandleObj = env->GetObjectField(obj,
-            gInputWindowHandleClassInfo.inputApplicationHandle);
+    jobject inputApplicationHandleObj =
+            env->GetObjectField(obj, gInputWindowHandleClassInfo.inputApplicationHandle);
     if (inputApplicationHandleObj) {
         std::shared_ptr<InputApplicationHandle> inputApplicationHandle =
                 android_view_InputApplicationHandle_getHandle(env, inputApplicationHandleObj);
         if (inputApplicationHandle != nullptr) {
             inputApplicationHandle->updateInfo();
-            mInfo.applicationInfo = *(inputApplicationHandle->getInfo());
+            windowInfo->applicationInfo = *(inputApplicationHandle->getInfo());
         }
         env->DeleteLocalRef(inputApplicationHandleObj);
     }
 
-    mInfo.replaceTouchableRegionWithCrop = env->GetBooleanField(obj,
-            gInputWindowHandleClassInfo.replaceTouchableRegionWithCrop);
+    windowInfo->replaceTouchableRegionWithCrop =
+            env->GetBooleanField(obj, gInputWindowHandleClassInfo.replaceTouchableRegionWithCrop);
 
-    jobject weakSurfaceCtrl = env->GetObjectField(obj,
-            gInputWindowHandleClassInfo.touchableRegionSurfaceControl.ctrl);
+    jobject weakSurfaceCtrl =
+            env->GetObjectField(obj,
+                                gInputWindowHandleClassInfo.touchableRegionSurfaceControl.ctrl);
     bool touchableRegionCropHandleSet = false;
     if (weakSurfaceCtrl) {
         // Promote java weak reference.
-        jobject strongSurfaceCtrl = env->CallObjectMethod(weakSurfaceCtrl,
-                gInputWindowHandleClassInfo.touchableRegionSurfaceControl.get);
+        jobject strongSurfaceCtrl =
+                env->CallObjectMethod(weakSurfaceCtrl,
+                                      gInputWindowHandleClassInfo.touchableRegionSurfaceControl
+                                              .get);
         if (strongSurfaceCtrl) {
-            jlong mNativeObject = env->GetLongField(strongSurfaceCtrl,
-                    gInputWindowHandleClassInfo.touchableRegionSurfaceControl.mNativeObject);
+            jlong mNativeObject =
+                    env->GetLongField(strongSurfaceCtrl,
+                                      gInputWindowHandleClassInfo.touchableRegionSurfaceControl
+                                              .mNativeObject);
             if (mNativeObject) {
                 auto ctrl = reinterpret_cast<SurfaceControl *>(mNativeObject);
-                mInfo.touchableRegionCropHandle = ctrl->getHandle();
+                windowInfo->touchableRegionCropHandle = ctrl->getHandle();
                 touchableRegionCropHandleSet = true;
             }
             env->DeleteLocalRef(strongSurfaceCtrl);
@@ -203,15 +194,15 @@
         env->DeleteLocalRef(weakSurfaceCtrl);
     }
     if (!touchableRegionCropHandleSet) {
-        mInfo.touchableRegionCropHandle.clear();
+        windowInfo->touchableRegionCropHandle.clear();
     }
 
     jobject windowTokenObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.windowToken);
     if (windowTokenObj) {
-        mInfo.windowToken = ibinderForJavaObject(env, windowTokenObj);
+        windowInfo->windowToken = ibinderForJavaObject(env, windowTokenObj);
         env->DeleteLocalRef(windowTokenObj);
     } else {
-        mInfo.windowToken.clear();
+        windowInfo->windowToken.clear();
     }
 
     ScopedLocalRef<jobject>
@@ -220,41 +211,16 @@
                                                        gInputWindowHandleClassInfo
                                                                .focusTransferTarget));
     if (focusTransferTargetObj.get()) {
-        mInfo.focusTransferTarget = ibinderForJavaObject(env, focusTransferTargetObj.get());
+        windowInfo->focusTransferTarget = ibinderForJavaObject(env, focusTransferTargetObj.get());
     } else {
-        mInfo.focusTransferTarget.clear();
+        windowInfo->focusTransferTarget.clear();
     }
 
-    env->DeleteLocalRef(obj);
-    return true;
-}
-
-
-// --- Global functions ---
-
-sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle(
-        JNIEnv* env, jobject inputWindowHandleObj) {
-    if (!inputWindowHandleObj) {
-        return NULL;
-    }
-
-    AutoMutex _l(gHandleMutex);
-
-    jlong ptr = env->GetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr);
-    NativeInputWindowHandle* handle;
-    if (ptr) {
-        handle = reinterpret_cast<NativeInputWindowHandle*>(ptr);
-    } else {
-        jweak objWeak = env->NewWeakGlobalRef(inputWindowHandleObj);
-        handle = new NativeInputWindowHandle(objWeak);
-        handle->incStrong((void*)android_view_InputWindowHandle_getHandle);
-        env->SetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr,
-                reinterpret_cast<jlong>(handle));
-    }
     return handle;
 }
 
-jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env, gui::WindowInfo windowInfo) {
+jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env,
+                                                      const gui::WindowInfo& windowInfo) {
     ScopedLocalRef<jobject>
             applicationHandle(env,
                               android_view_InputApplicationHandle_fromInputApplicationInfo(
@@ -337,18 +303,15 @@
 // --- JNI ---
 
 static void android_view_InputWindowHandle_nativeDispose(JNIEnv* env, jobject obj) {
-    AutoMutex _l(gHandleMutex);
-
     jlong ptr = env->GetLongField(obj, gInputWindowHandleClassInfo.ptr);
-    if (ptr) {
-        env->SetLongField(obj, gInputWindowHandleClassInfo.ptr, 0);
-
-        NativeInputWindowHandle* handle = reinterpret_cast<NativeInputWindowHandle*>(ptr);
-        handle->decStrong((void*)android_view_InputWindowHandle_getHandle);
+    if (!ptr) {
+        return;
     }
+    env->SetLongField(obj, gInputWindowHandleClassInfo.ptr, 0);
+    auto handle = reinterpret_cast<gui::WindowInfoHandle*>(ptr);
+    handle->decStrong((void*)android_view_InputWindowHandle_getHandle);
 }
 
-
 static const JNINativeMethod gInputWindowHandleMethods[] = {
     /* name, signature, funcPtr */
     { "nativeDispose", "()V",
diff --git a/core/jni/android_hardware_input_InputWindowHandle.h b/core/jni/android_hardware_input_InputWindowHandle.h
index 408e0f1..aa375e9 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.h
+++ b/core/jni/android_hardware_input_InputWindowHandle.h
@@ -24,24 +24,11 @@
 
 namespace android {
 
-class NativeInputWindowHandle : public gui::WindowInfoHandle {
-public:
-    NativeInputWindowHandle(jweak objWeak);
-    virtual ~NativeInputWindowHandle();
+sp<gui::WindowInfoHandle> android_view_InputWindowHandle_getHandle(JNIEnv* env,
+                                                                   jobject inputWindowHandleObj);
 
-    jobject getInputWindowHandleObjLocalRef(JNIEnv* env);
-
-    virtual bool updateInfo();
-
-private:
-    jweak mObjWeak;
-};
-
-extern sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle(
-        JNIEnv* env, jobject inputWindowHandleObj);
-
-extern jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env,
-                                                             gui::WindowInfo windowInfo);
+jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env,
+                                                      const gui::WindowInfo& windowInfo);
 
 } // namespace android
 
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 56292c3..d3bf36e 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -979,14 +979,16 @@
 
 static void nativeSetInputWindowInfo(JNIEnv* env, jclass clazz, jlong transactionObj,
         jlong nativeObject, jobject inputWindow) {
+    if (!inputWindow) {
+        jniThrowNullPointerException(env, "InputWindowHandle is null");
+        return;
+    }
+
     auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
 
-    sp<NativeInputWindowHandle> handle = android_view_InputWindowHandle_getHandle(
-            env, inputWindow);
-    handle->updateInfo();
-
+    sp<gui::WindowInfoHandle> info = android_view_InputWindowHandle_getHandle(env, inputWindow);
     auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
-    transaction->setInputWindowInfo(ctrl, *handle->getInfo());
+    transaction->setInputWindowInfo(ctrl, std::move(info));
 }
 
 static void nativeAddWindowInfosReportedListener(JNIEnv* env, jclass clazz, jlong transactionObj,
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index da1fffa..a2598f6 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -16,6 +16,7 @@
 
 package android.app;
 
+import static android.app.Flags.FLAG_PIC_ISOLATE_CACHE_BY_UID;
 import static android.app.PropertyInvalidatedCache.NONCE_UNSET;
 import static android.app.PropertyInvalidatedCache.MODULE_BLUETOOTH;
 import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
@@ -30,8 +31,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.app.PropertyInvalidatedCache.Args;
 import android.annotation.SuppressLint;
+import android.app.PropertyInvalidatedCache.Args;
+import android.os.Binder;
 import com.android.internal.os.ApplicationSharedMemory;
 
 import android.platform.test.annotations.IgnoreUnderRavenwood;
@@ -58,6 +60,7 @@
  */
 @SmallTest
 public class PropertyInvalidatedCacheTests {
+    @Rule
     public final CheckFlagsRule mCheckFlagsRule =
             DeviceFlagsValueProvider.createCheckFlagsRule();
 
@@ -455,8 +458,9 @@
     // Test the Args-style constructor.
     @Test
     public void testArgsConstructor() {
-        // Create a cache with a maximum of four entries.
-        TestCache cache = new TestCache(new Args(MODULE_TEST).api("init1").maxEntries(4),
+        // Create a cache with a maximum of four entries and non-isolated UIDs.
+        TestCache cache = new TestCache(new Args(MODULE_TEST)
+                .maxEntries(4).isolateUids(false).api("init1"),
                 new TestQuery());
 
         cache.invalidateCache();
@@ -570,4 +574,73 @@
             // Expected exception.
         }
     }
+
+    // Verify that a cache created with isolatedUids(true) separates out the results.
+    @RequiresFlagsEnabled(FLAG_PIC_ISOLATE_CACHE_BY_UID)
+    @Test
+    public void testIsolatedUids() {
+        TestCache cache = new TestCache(new Args(MODULE_TEST)
+                .maxEntries(4).isolateUids(true).api("testIsolatedUids").testMode(true),
+                new TestQuery());
+        cache.invalidateCache();
+        final int uid1 = 1;
+        final int uid2 = 2;
+
+        long token = Binder.setCallingWorkSourceUid(uid1);
+        try {
+            // Populate the cache for user 1
+            assertEquals("foo5", cache.query(5));
+            assertEquals(1, cache.getRecomputeCount());
+            assertEquals("foo5", cache.query(5));
+            assertEquals(1, cache.getRecomputeCount());
+            assertEquals("foo6", cache.query(6));
+            assertEquals(2, cache.getRecomputeCount());
+
+            // Populate the cache for user 2.  User 1 values are not reused.
+            Binder.setCallingWorkSourceUid(uid2);
+            assertEquals("foo5", cache.query(5));
+            assertEquals(3, cache.getRecomputeCount());
+            assertEquals("foo5", cache.query(5));
+            assertEquals(3, cache.getRecomputeCount());
+
+            // Verify that the cache for user 1 is still populated.
+            Binder.setCallingWorkSourceUid(uid1);
+            assertEquals("foo5", cache.query(5));
+            assertEquals(3, cache.getRecomputeCount());
+
+        } finally {
+            Binder.restoreCallingWorkSource(token);
+        }
+
+        // Repeat the test with a non-isolated cache.
+        cache = new TestCache(new Args(MODULE_TEST)
+                .maxEntries(4).isolateUids(false).api("testIsolatedUids2").testMode(true),
+                new TestQuery());
+        cache.invalidateCache();
+        token = Binder.setCallingWorkSourceUid(uid1);
+        try {
+            // Populate the cache for user 1
+            assertEquals("foo5", cache.query(5));
+            assertEquals(1, cache.getRecomputeCount());
+            assertEquals("foo5", cache.query(5));
+            assertEquals(1, cache.getRecomputeCount());
+            assertEquals("foo6", cache.query(6));
+            assertEquals(2, cache.getRecomputeCount());
+
+            // Populate the cache for user 2.  User 1 values are reused.
+            Binder.setCallingWorkSourceUid(uid2);
+            assertEquals("foo5", cache.query(5));
+            assertEquals(2, cache.getRecomputeCount());
+            assertEquals("foo5", cache.query(5));
+            assertEquals(2, cache.getRecomputeCount());
+
+            // Verify that the cache for user 1 is still populated.
+            Binder.setCallingWorkSourceUid(uid1);
+            assertEquals("foo5", cache.query(5));
+            assertEquals(2, cache.getRecomputeCount());
+
+        } finally {
+            Binder.restoreCallingWorkSource(token);
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/content/IntentTest.java b/core/tests/coretests/src/android/content/IntentTest.java
index 7bc4abd..d169ce3 100644
--- a/core/tests/coretests/src/android/content/IntentTest.java
+++ b/core/tests/coretests/src/android/content/IntentTest.java
@@ -37,10 +37,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
 /**
  *  Build/Install/Run:
  *   atest FrameworksCoreTests:IntentTest
@@ -61,12 +57,7 @@
     public void testReadFromParcelWithExtraIntentKeys() {
         Intent intent = new Intent("TEST_ACTION");
         intent.putExtra(TEST_EXTRA_NAME, new Intent(TEST_ACTION));
-        // Not an intent, don't count.
         intent.putExtra(TEST_EXTRA_NAME + "2", 1);
-        ArrayList<Intent> intents = new ArrayList<>();
-        intents.add(new Intent(TEST_ACTION));
-        intent.putParcelableArrayListExtra(TEST_EXTRA_NAME + "3", intents);
-        intent.setClipData(ClipData.newIntent("label", new Intent(TEST_ACTION)));
 
         intent.collectExtraIntentKeys();
         final Parcel parcel = Parcel.obtain();
@@ -77,7 +68,7 @@
 
         assertEquals(intent.getAction(), target.getAction());
         assertEquals(intent.getExtraIntentKeys(), target.getExtraIntentKeys());
-        assertThat(intent.getExtraIntentKeys()).hasSize(3);
+        assertThat(intent.getExtraIntentKeys()).hasSize(1);
     }
 
     @Test
@@ -96,37 +87,13 @@
     @RequiresFlagsEnabled(Flags.FLAG_PREVENT_INTENT_REDIRECT)
     public void testCollectExtraIntentKeys() {
         Intent intent = new Intent(TEST_ACTION);
-
-        Intent[] intents = new Intent[10];
-        for (int i = 0; i < intents.length; i++) {
-            intents[i] = new Intent("action" + i);
-        }
-        Intent[] intents2 = new Intent[2]; // intents[6-7]
-        System.arraycopy(intents, 6, intents2, 0, intents2.length);
-        ArrayList<Intent> intents3 = new ArrayList<>(2);
-        intents3.addAll(Arrays.asList(intents).subList(8, 10)); // intents[8-9]
-        intent.putExtra("key1", intents[0]);
-        intent.putExtra("array-key", intents2);
-        intent.setClipData(ClipData.newIntent("label2", intents[1]));
-        intent.putExtra("intkey", 1);
-        intents[0].putExtra("key3", intents[2]);
-        intents[0].setClipData(ClipData.newIntent("label4", intents[3]));
-        intents[0].putParcelableArrayListExtra("array-list-key", intents3);
-        intents[1].putExtra("key3", intents[4]);
-        intents[1].setClipData(ClipData.newIntent("label4", intents[5]));
-        intents[5].putExtra("intkey", 2);
+        Intent extraIntent = new Intent(TEST_ACTION, TEST_URI);
+        intent.putExtra(TEST_EXTRA_NAME, extraIntent);
 
         intent.collectExtraIntentKeys();
 
-        // collect all actions of nested intents.
-        final List<String> actions = new ArrayList<>();
-        intent.forEachNestedCreatorToken(intent1 -> {
-            actions.add(intent1.getAction());
-        });
-        assertThat(actions).hasSize(10);
-        for (int i = 0; i < intents.length; i++) {
-            assertThat(actions).contains("action" + i);
-        }
+        assertThat(intent.getExtraIntentKeys()).hasSize(1);
+        assertThat(intent.getExtraIntentKeys()).contains(TEST_EXTRA_NAME);
     }
 
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 999ce17..1f77abe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -19,6 +19,7 @@
 import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_IN;
 import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_OUT;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_GESTURE;
+import static com.android.wm.shell.shared.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -66,8 +67,6 @@
 
     private static final String TAG = BubbleBarLayerView.class.getSimpleName();
 
-    private static final float SCRIM_ALPHA = 0.2f;
-
     private final BubbleController mBubbleController;
     private final BubbleData mBubbleData;
     private final BubblePositioner mPositioner;
@@ -386,7 +385,7 @@
         if (show) {
             mScrimView.animate()
                     .setInterpolator(ALPHA_IN)
-                    .alpha(SCRIM_ALPHA)
+                    .alpha(BUBBLE_EXPANDED_SCRIM_ALPHA)
                     .start();
         } else {
             mScrimView.animate()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
index 4abb35c..193c593 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
@@ -16,8 +16,11 @@
 package com.android.wm.shell.common.pip
 
 import android.app.AppOpsManager
+import android.content.ComponentName
 import android.content.Context
 import android.content.pm.PackageManager
+import android.util.Pair
+import com.android.internal.annotations.VisibleForTesting
 import com.android.wm.shell.common.ShellExecutor
 
 class PipAppOpsListener(
@@ -27,10 +30,12 @@
 ) {
     private val mAppOpsManager: AppOpsManager = checkNotNull(
         mContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager)
+    private var mTopPipActivityInfoSupplier: (Context) -> Pair<ComponentName?, Int> =
+        PipUtils::getTopPipActivity
     private val mAppOpsChangedListener = AppOpsManager.OnOpChangedListener { _, packageName ->
         try {
             // Dismiss the PiP once the user disables the app ops setting for that package
-            val topPipActivityInfo = PipUtils.getTopPipActivity(mContext)
+            val topPipActivityInfo = mTopPipActivityInfoSupplier.invoke(mContext)
             val componentName = topPipActivityInfo.first ?: return@OnOpChangedListener
             val userId = topPipActivityInfo.second
             val appInfo = mContext.packageManager
@@ -75,4 +80,9 @@
         /** Dismisses the PIP window.  */
         fun dismissPip()
     }
+
+    @VisibleForTesting
+    fun setTopPipActivityInfoSupplier(supplier: (Context) -> Pair<ComponentName?, Int>) {
+        mTopPipActivityInfoSupplier = supplier
+    }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index a472f79..44fce81 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -836,14 +836,21 @@
     @Provides
     static Optional<DesktopImmersiveController> provideDesktopImmersiveController(
             Context context,
+            ShellInit shellInit,
             Transitions transitions,
             @DynamicOverride DesktopRepository desktopRepository,
             DisplayController displayController,
-            ShellTaskOrganizer shellTaskOrganizer) {
+            ShellTaskOrganizer shellTaskOrganizer,
+            ShellCommandHandler shellCommandHandler) {
         if (DesktopModeStatus.canEnterDesktopMode(context)) {
             return Optional.of(
                     new DesktopImmersiveController(
-                            transitions, desktopRepository, displayController, shellTaskOrganizer));
+                            shellInit,
+                            transitions,
+                            desktopRepository,
+                            displayController,
+                            shellTaskOrganizer,
+                            shellCommandHandler));
         }
         return Optional.empty();
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 3a4764d..3cd5df3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.os.Handler;
 
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.common.DisplayController;
@@ -41,6 +42,7 @@
 import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.dagger.WMShellBaseModule;
 import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
@@ -169,6 +171,8 @@
             PipParamsChangedForwarder pipParamsChangedForwarder,
             Optional<SplitScreenController> splitScreenControllerOptional,
             Optional<PipPerfHintController> pipPerfHintControllerOptional,
+            Optional<DesktopRepository> desktopRepositoryOptional,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             DisplayController displayController,
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
@@ -176,7 +180,8 @@
                 syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState,
                 pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
                 pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
-                splitScreenControllerOptional, pipPerfHintControllerOptional, displayController,
+                splitScreenControllerOptional, pipPerfHintControllerOptional,
+                desktopRepositoryOptional, rootTaskDisplayAreaOrganizer, displayController,
                 pipUiEventLogger, shellTaskOrganizer, mainExecutor);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index 8d1b15c1..78e676f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -22,6 +22,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.WindowManagerShellWrapper;
 import com.android.wm.shell.common.DisplayController;
@@ -214,6 +215,7 @@
             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
             Optional<SplitScreenController> splitScreenControllerOptional,
             Optional<PipPerfHintController> pipPerfHintControllerOptional,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             DisplayController displayController,
             PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
             @ShellMainThread ShellExecutor mainExecutor) {
@@ -221,8 +223,9 @@
                 syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState,
                 tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
                 pipSurfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
-                splitScreenControllerOptional, pipPerfHintControllerOptional, displayController,
-                pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+                splitScreenControllerOptional, pipPerfHintControllerOptional,
+                rootTaskDisplayAreaOrganizer, displayController, pipUiEventLogger,
+                shellTaskOrganizer, mainExecutor);
     }
 
     @WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
index f69aa6d..1acde73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
@@ -34,10 +34,13 @@
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TransitionHandler
 import com.android.wm.shell.transition.Transitions.TransitionObserver
 import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
+import java.io.PrintWriter
 
 /**
  * A controller to move tasks in/out of desktop's full immersive state where the task
@@ -45,27 +48,34 @@
  * be transient below the status bar like in fullscreen immersive mode.
  */
 class DesktopImmersiveController(
+    shellInit: ShellInit,
     private val transitions: Transitions,
     private val desktopRepository: DesktopRepository,
     private val displayController: DisplayController,
     private val shellTaskOrganizer: ShellTaskOrganizer,
+    private val shellCommandHandler: ShellCommandHandler,
     private val transactionSupplier: () -> SurfaceControl.Transaction,
 ) : TransitionHandler, TransitionObserver {
 
     constructor(
+        shellInit: ShellInit,
         transitions: Transitions,
         desktopRepository: DesktopRepository,
         displayController: DisplayController,
         shellTaskOrganizer: ShellTaskOrganizer,
+        shellCommandHandler: ShellCommandHandler,
     ) : this(
+        shellInit,
         transitions,
         desktopRepository,
         displayController,
         shellTaskOrganizer,
+        shellCommandHandler,
         { SurfaceControl.Transaction() }
     )
 
-    private var state: TransitionState? = null
+    @VisibleForTesting
+    var state: TransitionState? = null
 
     @VisibleForTesting
     val pendingExternalExitTransitions = mutableListOf<ExternalPendingExit>()
@@ -79,10 +89,21 @@
     /** A listener to invoke on animation changes during entry/exit. */
     var onTaskResizeAnimationListener: OnTaskResizeAnimationListener? = null
 
+    init {
+        shellInit.addInitCallback({ onInit() }, this)
+    }
+
+    fun onInit() {
+        shellCommandHandler.addDumpCallback(this::dump, this)
+    }
+
     /** Starts a transition to enter full immersive state inside the desktop. */
     fun moveTaskToImmersive(taskInfo: RunningTaskInfo) {
         if (inProgress) {
-            logV("Cannot start entry because transition already in progress.")
+            logV(
+                "Cannot start entry because transition(s) already in progress: %s",
+                getRunningTransitions()
+            )
             return
         }
         val wct = WindowContainerTransaction().apply {
@@ -100,7 +121,10 @@
 
     fun moveTaskToNonImmersive(taskInfo: RunningTaskInfo) {
         if (inProgress) {
-            logV("Cannot start exit because transition already in progress.")
+            logV(
+                "Cannot start exit because transition(s) already in progress: %s",
+                getRunningTransitions()
+            )
             return
         }
 
@@ -225,14 +249,19 @@
         finishCallback: Transitions.TransitionFinishCallback
     ): Boolean {
         val state = requireState()
-        if (transition != state.transition) return false
+        check(state.transition == transition) {
+            "Transition $transition did not match expected state=$state"
+        }
         logD("startAnimation transition=%s", transition)
         animateResize(
             targetTaskId = state.taskId,
             info = info,
             startTransaction = startTransaction,
             finishTransaction = finishTransaction,
-            finishCallback = finishCallback
+            finishCallback = {
+                finishCallback.onTransitionFinished(/* wct= */ null)
+                clearState()
+            },
         )
         return true
     }
@@ -242,12 +271,18 @@
         info: TransitionInfo,
         startTransaction: SurfaceControl.Transaction,
         finishTransaction: SurfaceControl.Transaction,
-        finishCallback: Transitions.TransitionFinishCallback
+        finishCallback: Transitions.TransitionFinishCallback,
     ) {
         logD("animateResize for task#%d", targetTaskId)
-        val change = info.changes.first { c ->
+        val change = info.changes.firstOrNull { c ->
             val taskInfo = c.taskInfo
-            return@first taskInfo != null && taskInfo.taskId == targetTaskId
+            return@firstOrNull taskInfo != null && taskInfo.taskId == targetTaskId
+        }
+        if (change == null) {
+            logD("Did not find change for task#%d to animate", targetTaskId)
+            startTransaction.apply()
+            finishCallback.onTransitionFinished(/* wct= */ null)
+            return
         }
         animateResizeChange(change, startTransaction, finishTransaction, finishCallback)
     }
@@ -288,7 +323,6 @@
                         .apply()
                     onTaskResizeAnimationListener?.onAnimationEnd(taskId)
                     finishCallback.onTransitionFinished(null /* wct */)
-                    clearState()
                 }
             )
             addUpdateListener { animation ->
@@ -357,8 +391,17 @@
         // Check if this is a direct immersive enter/exit transition.
         if (transition == state?.transition) {
             val state = requireState()
-            val startBounds = info.changes.first { c -> c.taskInfo?.taskId == state.taskId }
-                .startAbsBounds
+            val immersiveChange = info.changes.firstOrNull { c ->
+                c.taskInfo?.taskId == state.taskId
+            }
+            if (immersiveChange == null) {
+                logV(
+                    "Direct move for task#%d in %s direction missing immersive change.",
+                    state.taskId, state.direction
+                )
+                return
+            }
+            val startBounds = immersiveChange.startAbsBounds
             logV("Direct move for task#%d in %s direction verified", state.taskId, state.direction)
             when (state.direction) {
                 Direction.ENTER -> {
@@ -446,11 +489,30 @@
     private fun requireState(): TransitionState =
         state ?: error("Expected non-null transition state")
 
+    private fun getRunningTransitions(): List<IBinder> {
+        val running = mutableListOf<IBinder>()
+        state?.let {
+            running.add(it.transition)
+        }
+        pendingExternalExitTransitions.forEach {
+            running.add(it.transition)
+        }
+        return running
+    }
+
     private fun TransitionInfo.hasTaskChange(taskId: Int): Boolean =
         changes.any { c -> c.taskInfo?.taskId == taskId }
 
+    private fun dump(pw: PrintWriter, prefix: String) {
+        val innerPrefix = "$prefix  "
+        pw.println("${prefix}DesktopImmersiveController")
+        pw.println(innerPrefix + "state=" + state)
+        pw.println(innerPrefix + "pendingExternalExitTransitions=" + pendingExternalExitTransitions)
+    }
+
     /** The state of the currently running transition. */
-    private data class TransitionState(
+    @VisibleForTesting
+    data class TransitionState(
         val transition: IBinder,
         val displayId: Int,
         val taskId: Int,
@@ -483,7 +545,8 @@
         fun asExit(): Exit? = if (this is Exit) this else null
     }
 
-    private enum class Direction {
+    @VisibleForTesting
+    enum class Direction {
         ENTER, EXIT
     }
 
@@ -495,9 +558,10 @@
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
     }
 
-    private companion object {
+    companion object {
         private const val TAG = "DesktopImmersive"
 
-        private const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L
+        @VisibleForTesting
+        const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index fda709a..08ca55f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -102,6 +102,9 @@
     /* Tracks last bounds of task before toggled to stable bounds. */
     private val boundsBeforeMaximizeByTaskId = SparseArray<Rect>()
 
+    /* Tracks last bounds of task before it is minimized. */
+    private val boundsBeforeMinimizeByTaskId = SparseArray<Rect>()
+
     /* Tracks last bounds of task before toggled to immersive state. */
     private val boundsBeforeFullImmersiveByTaskId = SparseArray<Rect>()
 
@@ -462,6 +465,14 @@
     fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) =
         boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds))
 
+    /** Removes and returns the bounds saved before minimizing the given task. */
+    fun removeBoundsBeforeMinimize(taskId: Int): Rect? =
+        boundsBeforeMinimizeByTaskId.removeReturnOld(taskId)
+
+    /** Saves the bounds of the given task before minimizing. */
+    fun saveBoundsBeforeMinimize(taskId: Int, bounds: Rect?) =
+        boundsBeforeMinimizeByTaskId.set(taskId, Rect(bounds))
+
     /** Removes and returns the bounds saved before entering immersive with the given task. */
     fun removeBoundsBeforeFullImmersive(taskId: Int): Rect? =
         boundsBeforeFullImmersiveByTaskId.removeReturnOld(taskId)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 162879c..927fd88 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -1770,9 +1770,13 @@
         transition: IBinder,
         taskIdToMinimize: Int,
     ) {
-        val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize) ?: return
+        val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize)
         desktopTasksLimiter.ifPresent {
-            it.addPendingMinimizeChange(transition, taskToMinimize.displayId, taskToMinimize.taskId)
+            it.addPendingMinimizeChange(
+                transition = transition,
+                displayId = taskToMinimize?.displayId ?: DEFAULT_DISPLAY,
+                taskId = taskIdToMinimize
+            )
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index f0e3a2b..77af627 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -92,6 +92,12 @@
             }
             taskToMinimize.transitionInfo = info
             activeTransitionTokensAndTasks[transition] = taskToMinimize
+
+            // Save current bounds before minimizing in case we need to restore to it later.
+            val boundsBeforeMinimize = info.changes.find { change ->
+                change.taskInfo?.taskId == taskToMinimize.taskId }?.startAbsBounds
+            taskRepository.saveBoundsBeforeMinimize(taskToMinimize.taskId, boundsBeforeMinimize)
+
             this@DesktopTasksLimiter.minimizeTask(
                     taskToMinimize.displayId, taskToMinimize.taskId)
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index c4e63df..86c826a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.pip;
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -67,6 +68,7 @@
 import android.view.Display;
 import android.view.Surface;
 import android.view.SurfaceControl;
+import android.window.DisplayAreaInfo;
 import android.window.TaskOrganizer;
 import android.window.TaskSnapshot;
 import android.window.WindowContainerToken;
@@ -74,7 +76,9 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.ProtoLog;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.R;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ScreenshotUtils;
@@ -87,6 +91,7 @@
 import com.android.wm.shell.common.pip.PipPerfHintController;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.animation.Interpolators;
@@ -145,6 +150,8 @@
     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
     private final Optional<SplitScreenController> mSplitScreenOptional;
     @Nullable private final PipPerfHintController mPipPerfHintController;
+    private final Optional<DesktopRepository> mDesktopRepositoryOptional;
+    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
     protected final ShellTaskOrganizer mTaskOrganizer;
     protected final ShellExecutor mMainExecutor;
 
@@ -388,6 +395,8 @@
             @NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
             Optional<SplitScreenController> splitScreenOptional,
             Optional<PipPerfHintController> pipPerfHintControllerOptional,
+            Optional<DesktopRepository> desktopRepositoryOptional,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             @NonNull DisplayController displayController,
             @NonNull PipUiEventLogger pipUiEventLogger,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -414,6 +423,8 @@
                 new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
         mSplitScreenOptional = splitScreenOptional;
         mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
+        mDesktopRepositoryOptional = desktopRepositoryOptional;
+        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
         mTaskOrganizer = shellTaskOrganizer;
         mMainExecutor = mainExecutor;
 
@@ -741,10 +752,23 @@
     }
 
     /** Returns the bounds to restore to when exiting PIP mode. */
+    // TODO(b/377581840): Instead of manually tracking bounds, use bounds from Core.
     public Rect getExitDestinationBounds() {
+        if (isPipLaunchedInDesktopMode()) {
+            final Rect freeformBounds = mDesktopRepositoryOptional.get().removeBoundsBeforeMinimize(
+                    mTaskInfo.taskId);
+            return Objects.requireNonNullElseGet(freeformBounds, mPipBoundsState::getDisplayBounds);
+        }
         return mPipBoundsState.getDisplayBounds();
     }
 
+    /** Returns whether PiP was launched while in desktop mode. */
+    // TODO(377581840): Update this check to include non-minimized cases, e.g. split to PiP etc.
+    private boolean isPipLaunchedInDesktopMode() {
+        return Flags.enableDesktopWindowingPip() && mDesktopRepositoryOptional.isPresent()
+                && mDesktopRepositoryOptional.get().isMinimizedTask(mTaskInfo.taskId);
+    }
+
     private void exitLaunchIntoPipTask(WindowContainerTransaction wct) {
         wct.startTask(mTaskInfo.launchIntoPipHostTaskId, null /* ActivityOptions */);
         mTaskOrganizer.applyTransaction(wct);
@@ -1808,7 +1832,25 @@
      * and can be overridden to restore to an alternate windowing mode.
      */
     public int getOutPipWindowingMode() {
-        // By default, simply reset the windowing mode to undefined.
+        final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+                mTaskInfo.displayId);
+
+        // If PiP was launched while in desktop mode (we should return the task to freeform
+        // windowing mode):
+        // 1) If the display windowing mode is freeform, set windowing mode to undefined so it will
+        //    resolve the windowing mode to the display's windowing mode.
+        // 2) If the display windowing mode is not freeform, set windowing mode to freeform.
+        if (tdaInfo != null && isPipLaunchedInDesktopMode()) {
+            final int displayWindowingMode =
+                    tdaInfo.configuration.windowConfiguration.getWindowingMode();
+            if (displayWindowingMode == WINDOWING_MODE_FREEFORM) {
+                return WINDOWING_MODE_UNDEFINED;
+            } else {
+                return WINDOWING_MODE_FREEFORM;
+            }
+        }
+
+        // By default, or if the task is going to fullscreen, reset the windowing mode to undefined.
         return WINDOWING_MODE_UNDEFINED;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 28b91c6..8220ea5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -530,6 +530,13 @@
                 if (mFixedRotationState != FIXED_ROTATION_TRANSITION
                         && mFinishTransaction != null) {
                     mFinishTransaction.merge(tx);
+                    // Set window crop and position to destination bounds to avoid flickering.
+                    if (hasValidLeash) {
+                        mFinishTransaction.setWindowCrop(leash, destinationBounds.width(),
+                                destinationBounds.height());
+                        mFinishTransaction.setPosition(leash, destinationBounds.left,
+                                destinationBounds.top);
+                    }
                 }
             } else {
                 wct = new WindowContainerTransaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index 614ef2a..fcba461 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -21,6 +21,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
@@ -61,6 +62,7 @@
             @NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
             Optional<SplitScreenController> splitScreenOptional,
             Optional<PipPerfHintController> pipPerfHintControllerOptional,
+            @NonNull RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
             @NonNull DisplayController displayController,
             @NonNull PipUiEventLogger pipUiEventLogger,
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -68,8 +70,9 @@
         super(context, syncTransactionQueue, pipTransitionState, pipBoundsState,
                 pipDisplayLayoutState, boundsHandler, pipMenuController, pipAnimationController,
                 surfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
-                splitScreenOptional, pipPerfHintControllerOptional, displayController,
-                pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+                splitScreenOptional, pipPerfHintControllerOptional, Optional.empty(),
+                rootTaskDisplayAreaOrganizer, displayController, pipUiEventLogger,
+                shellTaskOrganizer, mainExecutor);
         mTvPipTransition = tvPipTransition;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index ea783e9..3caad09 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_PIP;
@@ -230,6 +231,11 @@
             // If there is no PiP change, exit this transition handler and potentially try others.
             if (pipChange == null) return false;
 
+            // Other targets might have default transforms applied that are not relevant when
+            // playing PiP transitions, so reset those transforms if needed.
+            prepareOtherTargetTransforms(info, startTransaction, finishTransaction);
+
+            // Update the PipTransitionState while supplying the PiP leash and token to be cached.
             Bundle extra = new Bundle();
             extra.putParcelable(PIP_TASK_TOKEN, pipChange.getContainer());
             extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash());
@@ -341,17 +347,21 @@
                             (destinationBounds.height() - overlaySize) / 2f);
         }
 
-        final int startRotation = pipChange.getStartRotation();
-        final int endRotation = mPipDisplayLayoutState.getRotation();
-        final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
-                : startRotation - endRotation;
+        final int delta = getFixedRotationDelta(info, pipChange);
         if (delta != ROTATION_0) {
-            mPipTransitionState.setInFixedRotation(true);
-            handleBoundsEnterFixedRotation(pipChange, pipActivityChange, endRotation);
+            // Update transition target changes in place to prepare for fixed rotation.
+            handleBoundsEnterFixedRotation(info, pipChange, pipActivityChange);
         }
 
+        // Update the src-rect-hint in params in place, to set up initial animator transform.
+        Rect sourceRectHint = getAdjustedSourceRectHint(info, pipChange, pipActivityChange);
+        pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint().set(sourceRectHint);
+
+        // Config-at-end transitions need to have their activities transformed before starting
+        // the animation; this makes the buffer seem like it's been updated to final size.
         prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange,
                 pipActivityChange);
+
         startTransaction.merge(finishTransaction);
         PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
                 startTransaction, finishTransaction, destinationBounds, delta);
@@ -387,55 +397,36 @@
             return false;
         }
 
+        final SurfaceControl pipLeash = getLeash(pipChange);
         final Rect startBounds = pipChange.getStartAbsBounds();
         final Rect endBounds = pipChange.getEndAbsBounds();
-
         final PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
-        final float aspectRatio = mPipBoundsAlgorithm.getAspectRatioOrDefault(params);
-        final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds,
-                endBounds);
-        final Rect adjustedSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint)
-                : PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio);
+        final Rect adjustedSourceRectHint = getAdjustedSourceRectHint(info, pipChange,
+                pipActivityChange);
 
-        final SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
-
-        // For opening type transitions, if there is a change of mode TO_FRONT/OPEN,
-        // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f
-        // by the Transitions framework to simplify Task opening transitions.
-        if (TransitionUtil.isOpeningType(info.getType())) {
-            for (TransitionInfo.Change change : info.getChanges()) {
-                if (change.getLeash() == null) continue;
-                if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
-                    startTransaction.setAlpha(change.getLeash(), 1f);
-                }
-            }
-        }
-
-        final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
-        final int startRotation = pipChange.getStartRotation();
-        final int endRotation = fixedRotationChange != null
-                ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED;
-        final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
-                : startRotation - endRotation;
-
+        final int delta = getFixedRotationDelta(info, pipChange);
         if (delta != ROTATION_0) {
-            mPipTransitionState.setInFixedRotation(true);
-            handleBoundsEnterFixedRotation(pipChange, pipActivityChange,
-                    fixedRotationChange.getEndFixedRotation());
+            // Update transition target changes in place to prepare for fixed rotation.
+            handleBoundsEnterFixedRotation(info, pipChange, pipActivityChange);
         }
 
         PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
                 startTransaction, finishTransaction, endBounds, delta);
-        if (sourceRectHint == null) {
-            // update the src-rect-hint in params in place, to set up initial animator transform.
-            params.getSourceRectHint().set(adjustedSourceRectHint);
+        if (PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds, endBounds) == null) {
+            // If app provided src-rect-hint is invalid, use app icon overlay.
             animator.setAppIconContentOverlay(
                     mContext, startBounds, endBounds, pipChange.getTaskInfo().topActivityInfo,
                     mPipBoundsState.getLauncherState().getAppIconSizePx());
         }
 
+        // Update the src-rect-hint in params in place, to set up initial animator transform.
+        params.getSourceRectHint().set(adjustedSourceRectHint);
+
+        // Config-at-end transitions need to have their activities transformed before starting
+        // the animation; this makes the buffer seem like it's been updated to final size.
         prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange,
                 pipActivityChange);
+
         animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange));
         animator.setAnimationEndCallback(() -> {
             if (animator.getContentOverlayLeash() != null) {
@@ -457,11 +448,22 @@
         animator.start();
     }
 
-    private void handleBoundsEnterFixedRotation(TransitionInfo.Change pipTaskChange,
-            TransitionInfo.Change pipActivityChange, int endRotation) {
-        final Rect endBounds = pipTaskChange.getEndAbsBounds();
-        final Rect endActivityBounds = pipActivityChange.getEndAbsBounds();
-        int startRotation = pipTaskChange.getStartRotation();
+    private void handleBoundsEnterFixedRotation(TransitionInfo info,
+            TransitionInfo.Change outPipTaskChange,
+            TransitionInfo.Change outPipActivityChange) {
+        final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
+        final Rect endBounds = outPipTaskChange.getEndAbsBounds();
+        final Rect endActivityBounds = outPipActivityChange.getEndAbsBounds();
+        int startRotation = outPipTaskChange.getStartRotation();
+        int endRotation = fixedRotationChange != null
+                ? fixedRotationChange.getEndFixedRotation() : mPipDisplayLayoutState.getRotation();
+
+        if (startRotation == endRotation) {
+            return;
+        }
+
+        // This is used by display change listeners to respond properly to fixed rotation.
+        mPipTransitionState.setInFixedRotation(true);
 
         // Cache the task to activity offset to potentially restore later.
         Point activityEndOffset = new Point(endActivityBounds.left - endBounds.left,
@@ -490,15 +492,15 @@
                 endBounds.top + activityEndOffset.y);
     }
 
-    private void handleExpandFixedRotation(TransitionInfo.Change pipTaskChange, int endRotation) {
-        final Rect endBounds = pipTaskChange.getEndAbsBounds();
+    private void handleExpandFixedRotation(TransitionInfo.Change outPipTaskChange, int delta) {
+        final Rect endBounds = outPipTaskChange.getEndAbsBounds();
         final int width = endBounds.width();
         final int height = endBounds.height();
         final int left = endBounds.left;
         final int top = endBounds.top;
         int newTop, newLeft;
 
-        if (endRotation == Surface.ROTATION_90) {
+        if (delta == Surface.ROTATION_90) {
             newLeft = top;
             newTop = -(left + width);
         } else {
@@ -585,15 +587,11 @@
         final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, endBounds,
                 startBounds);
 
-        final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
-        final int startRotation = pipChange.getStartRotation();
-        final int endRotation = fixedRotationChange != null
-                ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED;
-        final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
-                : endRotation - startRotation;
-
+        // We define delta = startRotation - endRotation, so we need to flip the sign.
+        final int delta = -getFixedRotationDelta(info, pipChange);
         if (delta != ROTATION_0) {
-            handleExpandFixedRotation(pipChange, endRotation);
+            // Update PiP target change in place to prepare for fixed rotation;
+            handleExpandFixedRotation(pipChange, delta);
         }
 
         PipExpandAnimator animator = new PipExpandAnimator(mContext, pipLeash,
@@ -661,6 +659,72 @@
         return null;
     }
 
+    @NonNull
+    private Rect getAdjustedSourceRectHint(@NonNull TransitionInfo info,
+            @NonNull TransitionInfo.Change pipTaskChange,
+            @NonNull TransitionInfo.Change pipActivityChange) {
+        final Rect startBounds = pipTaskChange.getStartAbsBounds();
+        final Rect endBounds = pipTaskChange.getEndAbsBounds();
+        final PictureInPictureParams params = pipTaskChange.getTaskInfo().pictureInPictureParams;
+
+        // Get the source-rect-hint provided by the app and check its validity; null if invalid.
+        final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds,
+                endBounds);
+
+        final Rect adjustedSourceRectHint = new Rect();
+        if (sourceRectHint != null) {
+            adjustedSourceRectHint.set(sourceRectHint);
+            // If multi-activity PiP, use the parent task before PiP to retrieve display cutouts;
+            // then, offset the valid app provided source rect hint by the cutout insets.
+            // For single-activity PiP, just use the pinned task to get the cutouts instead.
+            TransitionInfo.Change parentBeforePip = pipActivityChange.getLastParent() != null
+                    ? getChangeByToken(info, pipActivityChange.getLastParent()) : null;
+            Rect cutoutInsets = parentBeforePip != null
+                    ? parentBeforePip.getTaskInfo().displayCutoutInsets
+                    : pipTaskChange.getTaskInfo().displayCutoutInsets;
+            if (cutoutInsets != null
+                    && getFixedRotationDelta(info, pipTaskChange) == ROTATION_90) {
+                adjustedSourceRectHint.offset(cutoutInsets.left, cutoutInsets.top);
+            }
+        } else {
+            // For non-valid app provided src-rect-hint, calculate one to crop into during
+            // app icon overlay animation.
+            float aspectRatio = mPipBoundsAlgorithm.getAspectRatioOrDefault(params);
+            adjustedSourceRectHint.set(
+                    PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio));
+        }
+        return adjustedSourceRectHint;
+    }
+
+    @Surface.Rotation
+    private int getFixedRotationDelta(@NonNull TransitionInfo info,
+            @NonNull TransitionInfo.Change pipChange) {
+        TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
+        int startRotation = pipChange.getStartRotation();
+        int endRotation = fixedRotationChange != null
+                ? fixedRotationChange.getEndFixedRotation() : mPipDisplayLayoutState.getRotation();
+        int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
+                : startRotation - endRotation;
+        return delta;
+    }
+
+    private void prepareOtherTargetTransforms(TransitionInfo info,
+            SurfaceControl.Transaction startTransaction,
+            SurfaceControl.Transaction finishTransaction) {
+        // For opening type transitions, if there is a change of mode TO_FRONT/OPEN,
+        // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f
+        // by the Transitions framework to simplify Task opening transitions.
+        if (TransitionUtil.isOpeningType(info.getType())) {
+            for (TransitionInfo.Change change : info.getChanges()) {
+                if (change.getLeash() == null) continue;
+                if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
+                    startTransaction.setAlpha(change.getLeash(), 1f);
+                }
+            }
+        }
+
+    }
+
     private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
             @NonNull TransitionRequestInfo request) {
         // cache the original task token to check for multi-activity case later
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index cc0e1df..19a73f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -55,7 +55,6 @@
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_LAUNCHER;
-import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
@@ -1099,16 +1098,11 @@
 
     void setSideStagePosition(@SplitPosition int sideStagePosition,
             @Nullable WindowContainerTransaction wct) {
-        setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
-    }
-
-    private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
-            @Nullable WindowContainerTransaction wct) {
         if (mSideStagePosition == sideStagePosition) return;
         mSideStagePosition = sideStagePosition;
         sendOnStagePositionChanged();
 
-        if (mSideStage.mVisible && updateBounds) {
+        if (mSideStage.mVisible) {
             if (wct == null) {
                 // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
                 onLayoutSizeChanged(mSplitLayout);
@@ -1199,6 +1193,7 @@
         if (!isSplitActive()) return;
 
         final WindowContainerTransaction wct = new WindowContainerTransaction();
+        setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
         applyExitSplitScreen(childrenToTop, wct, exitReason);
     }
 
@@ -1598,6 +1593,13 @@
         }
         if (present) {
             updateRecentTasksSplitPair();
+        } else if (mMainStage.getChildCount() == 0 && mSideStage.getChildCount() == 0) {
+            mRecentTasks.ifPresent(recentTasks -> {
+                // remove the split pair mapping from recentTasks, and disable further updates
+                // to splits in the recents until we enter split again.
+                recentTasks.removeSplitPair(taskId);
+            });
+            exitSplitScreen(mMainStage, EXIT_REASON_ROOT_TASK_VANISHED);
         }
 
         for (int i = mListeners.size() - 1; i >= 0; --i) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java
new file mode 100644
index 0000000..b9490b8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.common.ShellExecutor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test against {@link PipAppOpsListener}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipAppOpsListenerTest {
+
+    @Mock private Context mMockContext;
+    @Mock private PackageManager mMockPackageManager;
+    @Mock private AppOpsManager mMockAppOpsManager;
+    @Mock private PipAppOpsListener.Callback mMockCallback;
+    @Mock private ShellExecutor mMockExecutor;
+
+    private PipAppOpsListener mPipAppOpsListener;
+
+    private ArgumentCaptor<AppOpsManager.OnOpChangedListener> mOnOpChangedListenerCaptor;
+    private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
+    private Pair<ComponentName, Integer> mTopPipActivity;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockContext.getSystemService(Context.APP_OPS_SERVICE))
+                .thenReturn(mMockAppOpsManager);
+        mOnOpChangedListenerCaptor = ArgumentCaptor.forClass(
+                AppOpsManager.OnOpChangedListener.class);
+        mRunnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+    }
+
+    @Test
+    public void onActivityPinned_registerAppOpsListener() {
+        String packageName = "com.android.test.pip";
+        mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+
+        mPipAppOpsListener.onActivityPinned(packageName);
+
+        verify(mMockAppOpsManager).startWatchingMode(
+                eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName),
+                any(AppOpsManager.OnOpChangedListener.class));
+    }
+
+    @Test
+    public void onActivityUnpinned_unregisterAppOpsListener() {
+        mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+
+        mPipAppOpsListener.onActivityUnpinned();
+
+        verify(mMockAppOpsManager).stopWatchingMode(any(AppOpsManager.OnOpChangedListener.class));
+    }
+
+    @Test
+    public void disablePipAppOps_dismissPip() throws PackageManager.NameNotFoundException {
+        String packageName = "com.android.test.pip";
+        mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+        // Set up the top pip activity info as mTopPipActivity
+        mTopPipActivity = new Pair<>(new ComponentName(packageName, "PipActivity"), 0);
+        mPipAppOpsListener.setTopPipActivityInfoSupplier(this::getTopPipActivity);
+        // Set up the application info as mApplicationInfo
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.packageName = packageName;
+        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenReturn(applicationInfo);
+        // Mock the mode to be **not** allowed
+        when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), eq(packageName)))
+                .thenReturn(AppOpsManager.MODE_DEFAULT);
+        // Set up the initial state
+        mPipAppOpsListener.onActivityPinned(packageName);
+        verify(mMockAppOpsManager).startWatchingMode(
+                eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName),
+                mOnOpChangedListenerCaptor.capture());
+        AppOpsManager.OnOpChangedListener opChangedListener = mOnOpChangedListenerCaptor.getValue();
+
+        opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE),
+                packageName);
+
+        verify(mMockExecutor).execute(mRunnableArgumentCaptor.capture());
+        Runnable runnable = mRunnableArgumentCaptor.getValue();
+        runnable.run();
+        verify(mMockCallback).dismissPip();
+    }
+
+    @Test
+    public void disablePipAppOps_differentPackage_doNothing()
+            throws PackageManager.NameNotFoundException {
+        String packageName = "com.android.test.pip";
+        mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+        // Set up the top pip activity info as mTopPipActivity
+        mTopPipActivity = new Pair<>(new ComponentName(packageName, "PipActivity"), 0);
+        mPipAppOpsListener.setTopPipActivityInfoSupplier(this::getTopPipActivity);
+        // Set up the application info as mApplicationInfo
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.packageName = packageName + ".modified";
+        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenReturn(applicationInfo);
+        // Mock the mode to be **not** allowed
+        when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), eq(packageName)))
+                .thenReturn(AppOpsManager.MODE_DEFAULT);
+        // Set up the initial state
+        mPipAppOpsListener.onActivityPinned(packageName);
+        verify(mMockAppOpsManager).startWatchingMode(
+                eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName),
+                mOnOpChangedListenerCaptor.capture());
+        AppOpsManager.OnOpChangedListener opChangedListener = mOnOpChangedListenerCaptor.getValue();
+
+        opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE),
+                packageName);
+
+        verifyZeroInteractions(mMockExecutor);
+    }
+
+    @Test
+    public void disablePipAppOps_nameNotFound_unregisterAppOpsListener()
+            throws PackageManager.NameNotFoundException {
+        String packageName = "com.android.test.pip";
+        mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+        // Set up the top pip activity info as mTopPipActivity
+        mTopPipActivity = new Pair<>(new ComponentName(packageName, "PipActivity"), 0);
+        mPipAppOpsListener.setTopPipActivityInfoSupplier(this::getTopPipActivity);
+        // Set up the application info as mApplicationInfo
+        ApplicationInfo applicationInfo = new ApplicationInfo();
+        applicationInfo.packageName = packageName;
+        when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+                .thenThrow(PackageManager.NameNotFoundException.class);
+        // Mock the mode to be **not** allowed
+        when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), eq(packageName)))
+                .thenReturn(AppOpsManager.MODE_DEFAULT);
+        // Set up the initial state
+        mPipAppOpsListener.onActivityPinned(packageName);
+        verify(mMockAppOpsManager).startWatchingMode(
+                eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName),
+                mOnOpChangedListenerCaptor.capture());
+        AppOpsManager.OnOpChangedListener opChangedListener = mOnOpChangedListenerCaptor.getValue();
+
+        opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE),
+                packageName);
+
+        verify(mMockAppOpsManager).stopWatchingMode(any(AppOpsManager.OnOpChangedListener.class));
+    }
+
+    private Pair<ComponentName, Integer> getTopPipActivity(Context context) {
+        return mTopPipActivity;
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
index e05a0b5..a4f4d05 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
@@ -15,6 +15,7 @@
  */
 package com.android.wm.shell.desktopmode
 
+import android.animation.AnimatorTestRule
 import android.app.ActivityManager.RunningTaskInfo
 import android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS
 import android.graphics.Rect
@@ -24,6 +25,7 @@
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.Surface
 import android.view.SurfaceControl
@@ -43,6 +45,7 @@
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.util.StubTransaction
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
@@ -64,17 +67,19 @@
  * Usage: atest WMShellUnitTests:DesktopImmersiveControllerTest
  */
 @SmallTest
+@TestableLooper.RunWithLooper
 @RunWith(AndroidTestingRunner::class)
 class DesktopImmersiveControllerTest : ShellTestCase() {
 
     @JvmField @Rule val setFlagsRule = SetFlagsRule()
+    @JvmField @Rule val animatorTestRule = AnimatorTestRule(this)
 
     @Mock private lateinit var mockTransitions: Transitions
     private lateinit var desktopRepository: DesktopRepository
     @Mock private lateinit var mockDisplayController: DisplayController
     @Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
     @Mock private lateinit var mockDisplayLayout: DisplayLayout
-    private val transactionSupplier = { SurfaceControl.Transaction() }
+    private val transactionSupplier = { StubTransaction() }
 
     private lateinit var controller: DesktopImmersiveController
 
@@ -89,10 +94,12 @@
             (invocation.getArgument(0) as Rect).set(STABLE_BOUNDS)
         }
         controller = DesktopImmersiveController(
+            shellInit = mock(),
             transitions = mockTransitions,
             desktopRepository = desktopRepository,
             displayController = mockDisplayController,
             shellTaskOrganizer = mockShellTaskOrganizer,
+            shellCommandHandler = mock(),
             transactionSupplier = transactionSupplier,
         )
     }
@@ -672,6 +679,60 @@
         assertThat(controller.isImmersiveChange(transition, change)).isTrue()
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+    fun externalAnimateResizeChange_doesNotCleanUpPendingTransitionState() {
+        val task = createFreeformTask()
+        val mockBinder = mock(IBinder::class.java)
+        whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)))
+            .thenReturn(mockBinder)
+        desktopRepository.setTaskInFullImmersiveState(
+            displayId = task.displayId,
+            taskId = task.taskId,
+            immersive = true
+        )
+
+        controller.moveTaskToNonImmersive(task)
+
+        controller.animateResizeChange(
+            change = TransitionInfo.Change(task.token, SurfaceControl()).apply {
+                taskInfo = task
+            },
+            startTransaction = StubTransaction(),
+            finishTransaction = StubTransaction(),
+            finishCallback = { }
+        )
+        animatorTestRule.advanceTimeBy(DesktopImmersiveController.FULL_IMMERSIVE_ANIM_DURATION_MS)
+
+        assertThat(controller.state).isNotNull()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+    fun startAnimation_missingChange_clearsState() {
+        val task = createFreeformTask()
+        val mockBinder = mock(IBinder::class.java)
+        whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)))
+            .thenReturn(mockBinder)
+        desktopRepository.setTaskInFullImmersiveState(
+            displayId = task.displayId,
+            taskId = task.taskId,
+            immersive = false
+        )
+
+        controller.moveTaskToImmersive(task)
+
+        controller.startAnimation(
+            transition = mockBinder,
+            info = createTransitionInfo(changes = emptyList()),
+            startTransaction = StubTransaction(),
+            finishTransaction = StubTransaction(),
+            finishCallback = {}
+        )
+
+        assertThat(controller.state).isNull()
+    }
+
     private fun createTransitionInfo(
         @TransitionType type: Int = TRANSIT_CHANGE,
         @TransitionFlags flags: Int = 0,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 414c1a6..7f790d5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -936,6 +936,28 @@
     }
 
     @Test
+    fun saveBoundsBeforeMinimize_boundsSavedByTaskId() {
+        val taskId = 1
+        val bounds = Rect(0, 0, 200, 200)
+
+        repo.saveBoundsBeforeMinimize(taskId, bounds)
+
+        assertThat(repo.removeBoundsBeforeMinimize(taskId)).isEqualTo(bounds)
+    }
+
+    @Test
+    fun removeBoundsBeforeMinimize_returnsNullAfterBoundsRemoved() {
+        val taskId = 1
+        val bounds = Rect(0, 0, 200, 200)
+        repo.saveBoundsBeforeMinimize(taskId, bounds)
+        repo.removeBoundsBeforeMinimize(taskId)
+
+        val boundsBeforeMinimize = repo.removeBoundsBeforeMinimize(taskId)
+
+        assertThat(boundsBeforeMinimize).isNull()
+    }
+
+    @Test
     fun getExpandedTasksOrdered_returnsFreeformTasksInCorrectOrder() {
         repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 3, isVisible = true)
         repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 01b69ae..456b50d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -17,6 +17,7 @@
 package com.android.wm.shell.desktopmode
 
 import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.Rect
 import android.os.Binder
 import android.os.Handler
 import android.platform.test.annotations.DisableFlags
@@ -24,8 +25,10 @@
 import android.platform.test.flag.junit.SetFlagsRule
 import android.testing.AndroidTestingRunner
 import android.view.Display.DEFAULT_DISPLAY
+import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_OPEN
 import android.view.WindowManager.TRANSIT_TO_BACK
+import android.window.TransitionInfo
 import android.window.WindowContainerTransaction
 import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK
 import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
@@ -63,6 +66,7 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.`when`
 import org.mockito.kotlin.eq
@@ -235,6 +239,30 @@
     }
 
     @Test
+    fun onTransitionReady_pendingTransition_changeTaskToBack_boundsSaved() {
+        val bounds = Rect(0, 0, 200, 200)
+        val transition = Binder()
+        val task = setUpFreeformTask()
+        desktopTasksLimiter.addPendingMinimizeChange(
+            transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+
+        val change = TransitionInfo.Change(task.token, mock(SurfaceControl::class.java)).apply {
+            mode = TRANSIT_TO_BACK
+            taskInfo = task
+            setStartAbsBounds(bounds)
+        }
+        desktopTasksLimiter.getTransitionObserver().onTransitionReady(
+            transition,
+            TransitionInfo(TRANSIT_OPEN, TransitionInfo.FLAG_NONE).apply { addChange(change) },
+            StubTransaction() /* startTransaction */,
+            StubTransaction() /* finishTransaction */)
+
+        assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue()
+        assertThat(desktopTaskRepo.removeBoundsBeforeMinimize(taskId = task.taskId)).isEqualTo(
+            bounds)
+    }
+
+    @Test
     fun onTransitionReady_transitionMergedFromPending_taskIsMinimized() {
         val mergedTransition = Binder()
         val newTransition = Binder()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index bcb7461..5f58265 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -47,6 +47,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.wm.shell.MockSurfaceControlHelper;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
@@ -61,6 +62,7 @@
 import com.android.wm.shell.common.pip.PipSnapAlgorithm;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.SizeSpecSource;
+import com.android.wm.shell.desktopmode.DesktopRepository;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
@@ -90,6 +92,8 @@
     @Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
     @Mock private PipUiEventLogger mMockPipUiEventLogger;
     @Mock private Optional<SplitScreenController> mMockOptionalSplitScreen;
+    @Mock private Optional<DesktopRepository> mMockOptionalDesktopRepository;
+    @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
     @Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
     @Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
     private TestShellExecutor mMainExecutor;
@@ -120,8 +124,10 @@
                 mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController,
                 mMockPipSurfaceTransactionHelper, mMockPipTransitionController,
                 mMockPipParamsChangedForwarder, mMockOptionalSplitScreen,
-                Optional.empty() /* pipPerfHintControllerOptional */, mMockDisplayController,
-                mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor);
+                Optional.empty() /* pipPerfHintControllerOptional */,
+                mMockOptionalDesktopRepository, mRootTaskDisplayAreaOrganizer,
+                mMockDisplayController, mMockPipUiEventLogger, mMockShellTaskOrganizer,
+                mMainExecutor);
         mMainExecutor.flushAll();
         preparePipTaskOrg();
         preparePipSurfaceTransactionHelper();
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index f56667d..de40209 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -59,7 +59,7 @@
     ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument, @NonNull android.os.Bundle);
     method @NonNull public android.os.Bundle getExtras();
     method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
-    field public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
+    field public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue";
   }
 
 }
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
index 42c3c03..0826f04 100644
--- a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
@@ -39,7 +39,7 @@
      *
      * <p>See {@link #getResultDocument} for more information on extracting the return value.
      */
-    public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
+    public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue";
 
     /**
      * Returns the return value of the executed function.
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 52a21e2..ba2398c 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -51,7 +51,7 @@
     is_exported: true
     namespace: "media_tv"
     description: "Enables the following type constant in MediaRoute2Info: LINE_ANALOG, LINE_DIGITAL, AUX_LINE"
-    bug: "301713440"
+    bug: "375691732"
 }
 
 flag {
diff --git a/media/java/android/media/quality/AmbientBacklightSettings.java b/media/java/android/media/quality/AmbientBacklightSettings.java
index 391eb22..bb782bf 100644
--- a/media/java/android/media/quality/AmbientBacklightSettings.java
+++ b/media/java/android/media/quality/AmbientBacklightSettings.java
@@ -26,6 +26,7 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
+ * Settings for ambient backlight.
  * @hide
  */
 public class AmbientBacklightSettings implements Parcelable {
diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl
index e6c79dd..250d59b 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/IMediaQualityManager.aidl
@@ -30,20 +30,22 @@
  */
 interface IMediaQualityManager {
     PictureProfile createPictureProfile(in PictureProfile pp);
-    void updatePictureProfile(in long id, in PictureProfile pp);
-    void removePictureProfile(in long id);
-    PictureProfile getPictureProfileById(in long id);
+    void updatePictureProfile(in String id, in PictureProfile pp);
+    void removePictureProfile(in String id);
+    PictureProfile getPictureProfile(in int type, in String name);
     List<PictureProfile> getPictureProfilesByPackage(in String packageName);
     List<PictureProfile> getAvailablePictureProfiles();
-    List<PictureProfile> getAllPictureProfiles();
+    List<String> getPictureProfilePackageNames();
+    List<String> getPictureProfileAllowList();
+    void setPictureProfileAllowList(in List<String> packages);
 
     SoundProfile createSoundProfile(in SoundProfile pp);
-    void updateSoundProfile(in long id, in SoundProfile pp);
-    void removeSoundProfile(in long id);
-    SoundProfile getSoundProfileById(in long id);
+    void updateSoundProfile(in String id, in SoundProfile pp);
+    void removeSoundProfile(in String id);
+    SoundProfile getSoundProfileById(in String id);
     List<SoundProfile> getSoundProfilesByPackage(in String packageName);
     List<SoundProfile> getAvailableSoundProfiles();
-    List<SoundProfile> getAllSoundProfiles();
+    List<String> getSoundProfilePackageNames();
 
     void registerPictureProfileCallback(in IPictureProfileCallback cb);
     void registerSoundProfileCallback(in ISoundProfileCallback cb);
diff --git a/media/java/android/media/quality/IPictureProfileCallback.aidl b/media/java/android/media/quality/IPictureProfileCallback.aidl
index 05441cd..34aa2b0 100644
--- a/media/java/android/media/quality/IPictureProfileCallback.aidl
+++ b/media/java/android/media/quality/IPictureProfileCallback.aidl
@@ -17,6 +17,7 @@
 
 package android.media.quality;
 
+import android.media.quality.ParamCapability;
 import android.media.quality.PictureProfile;
 
 /**
@@ -24,7 +25,9 @@
  * @hide
  */
 oneway interface IPictureProfileCallback {
-    void onPictureProfileAdded(in long id, in PictureProfile p);
-    void onPictureProfileUpdated(in long id, in PictureProfile p);
-    void onPictureProfileRemoved(in long id, in PictureProfile p);
+    void onPictureProfileAdded(in String id, in PictureProfile p);
+    void onPictureProfileUpdated(in String id, in PictureProfile p);
+    void onPictureProfileRemoved(in String id, in PictureProfile p);
+    void onParamCapabilitiesChanged(in String id, in List<ParamCapability> caps);
+    void onError(in int err);
 }
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 38a2025..26d83ac 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -19,6 +19,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.media.tv.flags.Flags;
@@ -63,7 +64,7 @@
         mService = service;
         IPictureProfileCallback ppCallback = new IPictureProfileCallback.Stub() {
             @Override
-            public void onPictureProfileAdded(long profileId, PictureProfile profile) {
+            public void onPictureProfileAdded(String profileId, PictureProfile profile) {
                 synchronized (mLock) {
                     for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
                         // TODO: filter callback record
@@ -72,7 +73,7 @@
                 }
             }
             @Override
-            public void onPictureProfileUpdated(long profileId, PictureProfile profile) {
+            public void onPictureProfileUpdated(String profileId, PictureProfile profile) {
                 synchronized (mLock) {
                     for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
                         // TODO: filter callback record
@@ -81,7 +82,7 @@
                 }
             }
             @Override
-            public void onPictureProfileRemoved(long profileId, PictureProfile profile) {
+            public void onPictureProfileRemoved(String profileId, PictureProfile profile) {
                 synchronized (mLock) {
                     for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
                         // TODO: filter callback record
@@ -89,6 +90,24 @@
                     }
                 }
             }
+            @Override
+            public void onParamCapabilitiesChanged(String profileId, List<ParamCapability> caps) {
+                synchronized (mLock) {
+                    for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postParamCapabilitiesChanged(profileId, caps);
+                    }
+                }
+            }
+            @Override
+            public void onError(int err) {
+                synchronized (mLock) {
+                    for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postError(err);
+                    }
+                }
+            }
         };
         ISoundProfileCallback spCallback = new ISoundProfileCallback.Stub() {
             @Override
@@ -175,14 +194,17 @@
 
 
     /**
-     * Gets picture profile by given profile ID.
-     * @return the corresponding picture profile if available; {@code null} if the ID doesn't
-     *         exist or the profile is not accessible to the caller.
+     * Gets picture profile by given profile type and name.
+     *
+     * @return the corresponding picture profile if available; {@code null} if the name doesn't
+     *         exist.
      * @hide
      */
-    public PictureProfile getPictureProfileById(long profileId) {
+    @Nullable
+    public PictureProfile getPictureProfile(
+            @PictureProfile.ProfileType int type, @NonNull String name) {
         try {
-            return mService.getPictureProfileById(profileId);
+            return mService.getPictureProfile(type, name);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -190,11 +212,13 @@
 
 
     /**
-     * @SystemApi gets profiles that available to the given package
-     * @hide
+     * Gets profiles that available to the given package.
+     *
+     * @hide @SystemApi
      */
+    @NonNull
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
-    public List<PictureProfile> getPictureProfilesByPackage(String packageName) {
+    public List<PictureProfile> getPictureProfilesByPackage(@NonNull String packageName) {
         try {
             return mService.getPictureProfilesByPackage(packageName);
         } catch (RemoteException e) {
@@ -215,13 +239,16 @@
     }
 
     /**
-     * @SystemApi all stored picture profiles of all packages
-     * @hide
+     * Gets all package names whose picture profiles are available.
+     *
+     * @see #getPictureProfilesByPackage(String)
+     * @hide @SystemApi
      */
+    @NonNull
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
-    public List<PictureProfile> getAllPictureProfiles() {
+    public List<String> getPictureProfilePackageNames() {
         try {
-            return mService.getAllPictureProfiles();
+            return mService.getPictureProfilePackageNames();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -231,10 +258,12 @@
     /**
      * Creates a picture profile and store it in the system.
      *
-     * @return the stored profile with an assigned profile ID.
+     * @return the stored profile with an assigned profile ID. {@code null} if it's not created
+     * successfully.
      * @hide
      */
-    public PictureProfile createPictureProfile(PictureProfile pp) {
+    @Nullable
+    public PictureProfile createPictureProfile(@NonNull PictureProfile pp) {
         try {
             return mService.createPictureProfile(pp);
         } catch (RemoteException e) {
@@ -247,7 +276,7 @@
      * Updates an existing picture profile and store it in the system.
      * @hide
      */
-    public void updatePictureProfile(long profileId, PictureProfile pp) {
+    public void updatePictureProfile(@NonNull String profileId, @NonNull PictureProfile pp) {
         try {
             mService.updatePictureProfile(profileId, pp);
         } catch (RemoteException e) {
@@ -260,7 +289,7 @@
      * Removes a picture profile from the system.
      * @hide
      */
-    public void removePictureProfile(long profileId) {
+    public void removePictureProfile(@NonNull String profileId) {
         try {
             mService.removePictureProfile(profileId);
         } catch (RemoteException e) {
@@ -307,7 +336,7 @@
      *         exist or the profile is not accessible to the caller.
      * @hide
      */
-    public SoundProfile getSoundProfileById(long profileId) {
+    public SoundProfile getSoundProfileById(String profileId) {
         try {
             return mService.getSoundProfileById(profileId);
         } catch (RemoteException e) {
@@ -346,9 +375,9 @@
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
-    public List<SoundProfile> getAllSoundProfiles() {
+    public List<String> getSoundProfilePackageNames() {
         try {
-            return mService.getAllSoundProfiles();
+            return mService.getSoundProfilePackageNames();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -374,7 +403,7 @@
      * Updates an existing sound profile and store it in the system.
      * @hide
      */
-    public void updateSoundProfile(long profileId, SoundProfile sp) {
+    public void updateSoundProfile(String profileId, SoundProfile sp) {
         try {
             mService.updateSoundProfile(profileId, sp);
         } catch (RemoteException e) {
@@ -387,7 +416,7 @@
      * Removes a sound profile from the system.
      * @hide
      */
-    public void removeSoundProfile(long profileId) {
+    public void removeSoundProfile(String profileId) {
         try {
             mService.removeSoundProfile(profileId);
         } catch (RemoteException e) {
@@ -399,7 +428,8 @@
      * Gets capability information of the given parameters.
      * @hide
      */
-    public List<ParamCapability> getParamCapabilities(List<String> names) {
+    @NonNull
+    public List<ParamCapability> getParamCapabilities(@NonNull List<String> names) {
         try {
             return mService.getParamCapabilities(names);
         } catch (RemoteException e) {
@@ -408,7 +438,38 @@
     }
 
     /**
+     * Gets the allowlist of packages that can create and removed picture profiles
+     *
+     * @see #createPictureProfile(PictureProfile)
+     * @see #removePictureProfile(String)
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+    @NonNull
+    public List<String> getPictureProfileAllowList() {
+        try {
+            return mService.getPictureProfileAllowList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets the allowlist of packages that can create and removed picture profiles
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+    public void setPictureProfileAllowList(@NonNull List<String> packageNames) {
+        try {
+            mService.setPictureProfileAllowList(packageNames);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns {@code true} if media quality HAL is implemented; {@code false} otherwise.
+     * @hide
      */
     public boolean isSupported() {
         try {
@@ -581,7 +642,7 @@
             return mCallback;
         }
 
-        public void postPictureProfileAdded(final long id, PictureProfile profile) {
+        public void postPictureProfileAdded(final String id, PictureProfile profile) {
 
             mExecutor.execute(new Runnable() {
                 @Override
@@ -591,7 +652,7 @@
             });
         }
 
-        public void postPictureProfileUpdated(final long id, PictureProfile profile) {
+        public void postPictureProfileUpdated(final String id, PictureProfile profile) {
             mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
@@ -600,7 +661,7 @@
             });
         }
 
-        public void postPictureProfileRemoved(final long id, PictureProfile profile) {
+        public void postPictureProfileRemoved(final String id, PictureProfile profile) {
             mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
@@ -608,6 +669,24 @@
                 }
             });
         }
+
+        public void postParamCapabilitiesChanged(final String id, List<ParamCapability> caps) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onParamCapabilitiesChanged(id, caps);
+                }
+            });
+        }
+
+        public void postError(int error) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onError(error);
+                }
+            });
+        }
     }
 
     private static final class SoundProfileCallbackRecord {
@@ -681,24 +760,57 @@
      */
     public abstract static class PictureProfileCallback {
         /**
+         * This is invoked when a picture profile has been added.
+         *
+         * @param profileId the ID of the profile.
+         * @param profile the newly added profile.
          * @hide
          */
-        public void onPictureProfileAdded(long id, PictureProfile profile) {
+        public void onPictureProfileAdded(
+                @NonNull String profileId, @NonNull PictureProfile profile) {
         }
+
         /**
+         * This is invoked when a picture profile has been updated.
+         *
+         * @param profileId the ID of the profile.
+         * @param profile the profile with updated info.
          * @hide
          */
-        public void onPictureProfileUpdated(long id, PictureProfile profile) {
+        public void onPictureProfileUpdated(
+                @NonNull String profileId, @NonNull PictureProfile profile) {
         }
+
         /**
+         * This is invoked when a picture profile has been removed.
+         *
+         * @param profileId the ID of the profile.
+         * @param profile the removed profile.
          * @hide
          */
-        public void onPictureProfileRemoved(long id, PictureProfile profile) {
+        public void onPictureProfileRemoved(
+                @NonNull String profileId, @NonNull PictureProfile profile) {
         }
+
         /**
+         * This is invoked when an issue has occurred.
+         *
+         * @param errorCode the error code
          * @hide
          */
-        public void onError(int errorCode) {
+        public void onError(@PictureProfile.ErrorCode int errorCode) {
+        }
+
+        /**
+         * This is invoked when parameter capabilities has been changed due to status changes of the
+         * content.
+         *
+         * @param profileId the ID of the profile used by the media content.
+         * @param updatedCaps the updated capabilities.
+         * @hide
+         */
+        public void onParamCapabilitiesChanged(
+                @NonNull String profileId, @NonNull List<ParamCapability> updatedCaps) {
         }
     }
 
diff --git a/media/java/android/media/quality/ParamCapability.java b/media/java/android/media/quality/ParamCapability.java
index 70e8592..0b698a9 100644
--- a/media/java/android/media/quality/ParamCapability.java
+++ b/media/java/android/media/quality/ParamCapability.java
@@ -34,7 +34,7 @@
  * @hide
  */
 @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
-public class ParamCapability implements Parcelable {
+public final class ParamCapability implements Parcelable {
 
     /** @hide */
     @IntDef(flag = true, prefix = { "TYPE_" }, value = {
@@ -104,6 +104,7 @@
     @NonNull
     private final Bundle mCaps;
 
+    /** @hide */
     protected ParamCapability(Parcel in) {
         mName = in.readString();
         mIsSupported = in.readBoolean();
@@ -112,7 +113,7 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(mName);
         dest.writeBoolean(mIsSupported);
         dest.writeInt(mType);
@@ -124,6 +125,7 @@
         return 0;
     }
 
+    @NonNull
     public static final Creator<ParamCapability> CREATOR = new Creator<ParamCapability>() {
         @Override
         public ParamCapability createFromParcel(Parcel in) {
diff --git a/media/java/android/media/quality/PictureProfile.java b/media/java/android/media/quality/PictureProfile.java
index 8fb5712..2be47dd 100644
--- a/media/java/android/media/quality/PictureProfile.java
+++ b/media/java/android/media/quality/PictureProfile.java
@@ -71,6 +71,53 @@
      */
     public static final int TYPE_APPLICATION = 2;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = false, prefix = "ERROR_", value = {
+            ERROR_UNKNOWN,
+            ERROR_NO_PERMISSION,
+            ERROR_DUPLICATE,
+            ERROR_INVALID_ARGUMENT,
+            ERROR_NOT_ALLOWLISTED
+    })
+    public @interface ErrorCode {}
+
+    /**
+     * Error code for unknown errors.
+     * @hide
+     */
+    public static final int ERROR_UNKNOWN = 0;
+
+    /**
+     * Error code for missing necessary permission to handle the profiles.
+     * @hide
+     */
+    public static final int ERROR_NO_PERMISSION = 1;
+
+    /**
+     * Error code for creating a profile with existing profile type and name.
+     *
+     * @see #getProfileType()
+     * @see #getName()
+     * @hide
+     */
+    public static final int ERROR_DUPLICATE = 2;
+
+    /**
+     * Error code for invalid argument.
+     * @hide
+     */
+    public static final int ERROR_INVALID_ARGUMENT = 3;
+
+    /**
+     * Error code for the case when an operation requires an allowlist but the caller is not in the
+     * list.
+     *
+     * @see MediaQualityManager#getPictureProfileAllowList()
+     * @hide
+     */
+    public static final int ERROR_NOT_ALLOWLISTED = 4;
+
 
     private PictureProfile(@NonNull Parcel in) {
         mId = in.readString();
diff --git a/packages/SettingsLib/IntroPreference/Android.bp b/packages/SettingsLib/IntroPreference/Android.bp
index 155db18..8f9fb7a 100644
--- a/packages/SettingsLib/IntroPreference/Android.bp
+++ b/packages/SettingsLib/IntroPreference/Android.bp
@@ -29,5 +29,6 @@
     min_sdk_version: "21",
     apex_available: [
         "//apex_available:platform",
+        "com.android.healthfitness",
     ],
 }
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
index 155ee83..78e27fe 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
@@ -29,6 +29,7 @@
         "//apex_available:platform",
         "com.android.permission",
         "com.android.mediaprovider",
+        "com.android.healthfitness",
     ],
 }
 
@@ -51,5 +52,6 @@
         "//apex_available:platform",
         "com.android.permission",
         "com.android.mediaprovider",
+        "com.android.healthfitness",
     ],
 }
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index b4d81d6..7c478ac 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -353,7 +353,7 @@
     public void onDestroy() {
         mServiceHandler.getLooper().quit();
         mScreenshotHandler.getLooper().quit();
-        mBugreportSingleThreadExecutor.close();
+        mBugreportSingleThreadExecutor.shutdown();
         super.onDestroy();
     }
 
diff --git a/packages/SystemUI/README.md b/packages/SystemUI/README.md
index 2910bba..635a97e 100644
--- a/packages/SystemUI/README.md
+++ b/packages/SystemUI/README.md
@@ -87,7 +87,7 @@
 
 There are a few places where CommandQueue is used as a bus to communicate
 across sysui. Such as when StatusBar calls CommandQueue#recomputeDisableFlags.
-This is generally used a shortcut to directly trigger CommandQueue rather than
+This is generally used as a shortcut to directly trigger CommandQueue rather than
 calling StatusManager and waiting for the call to come back to IStatusBar.
 
 ### [com.android.systemui.util.NotificationChannels](/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java)
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 02b7667..207ed71 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -16,6 +16,16 @@
 }
 
 flag {
+   name: "multiuser_wifi_picker_tracker_support"
+   namespace: "systemui"
+   description: "Adds WifiPickerTracker support for multiple users to support when HSUM is enabled."
+   bug: "371586248"
+   metadata {
+        purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
    name: "udfps_view_performance"
    namespace: "systemui"
    description: "Decrease screen off blocking calls by waiting until the device is finished going to sleep before adding the udfps view."
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
index eeab232..163f4b3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
@@ -59,6 +59,7 @@
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.hideFromAccessibility
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
@@ -286,7 +287,10 @@
     Surface(
         modifier =
             Modifier.padding(top = 16.dp, bottom = 6.dp)
-                .semantics { contentDescription = dragHandleContentDescription }
+                .semantics {
+                    contentDescription = dragHandleContentDescription
+                    hideFromAccessibility()
+                }
                 .clickable { dialog.dismiss() },
         color = MaterialTheme.colorScheme.onSurfaceVariant,
         shape = MaterialTheme.shapes.extraLarge,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index e7b66c5..d976e8e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -693,8 +693,8 @@
     val fromState = updateStateInContent(transition.fromContent)
     val toState = updateStateInContent(transition.toContent)
 
-    reconcileStates(element, previousTransition)
-    reconcileStates(element, transition)
+    val previousUniqueState = reconcileStates(element, previousTransition, previousState = null)
+    reconcileStates(element, transition, previousState = previousUniqueState)
 
     // Remove the interruption values to all contents but the content(s) where the element will be
     // placed, to make sure that interruption deltas are computed only right after this interruption
@@ -721,12 +721,32 @@
 /**
  * Reconcile the state of [element] in the formContent and toContent of [transition] so that the
  * values before interruption have their expected values, taking shared transitions into account.
+ *
+ * @return the unique state this element had during [transition], `null` if it had multiple
+ *   different states (i.e. the shared animation was disabled).
  */
-private fun reconcileStates(element: Element, transition: TransitionState.Transition) {
-    val fromContentState = element.stateByContent[transition.fromContent] ?: return
-    val toContentState = element.stateByContent[transition.toContent] ?: return
+private fun reconcileStates(
+    element: Element,
+    transition: TransitionState.Transition,
+    previousState: Element.State?,
+): Element.State? {
+    fun reconcileWithPreviousState(state: Element.State) {
+        if (previousState != null && state.offsetBeforeInterruption == Offset.Unspecified) {
+            state.updateValuesBeforeInterruption(previousState)
+        }
+    }
+
+    val fromContentState = element.stateByContent[transition.fromContent]
+    val toContentState = element.stateByContent[transition.toContent]
+
+    if (fromContentState == null || toContentState == null) {
+        return (fromContentState ?: toContentState)
+            ?.also { reconcileWithPreviousState(it) }
+            ?.takeIf { it.offsetBeforeInterruption != Offset.Unspecified }
+    }
+
     if (!isSharedElementEnabled(element.key, transition)) {
-        return
+        return null
     }
 
     if (
@@ -735,13 +755,19 @@
     ) {
         // Element is shared and placed in fromContent only.
         toContentState.updateValuesBeforeInterruption(fromContentState)
-    } else if (
+        return fromContentState
+    }
+
+    if (
         toContentState.offsetBeforeInterruption != Offset.Unspecified &&
             fromContentState.offsetBeforeInterruption == Offset.Unspecified
     ) {
         // Element is shared and placed in toContent only.
         fromContentState.updateValuesBeforeInterruption(toContentState)
+        return toContentState
     }
+
+    return null
 }
 
 private fun Element.State.selfUpdateValuesBeforeInterruption() {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 4a90515..a301856 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -2638,4 +2638,58 @@
             assertWithMessage("Frame $i didn't replace Foo").that(numberOfPlacements).isEqualTo(0)
         }
     }
+
+    @Test
+    fun interruption_considerPreviousUniqueState() {
+        @Composable
+        fun SceneScope.Foo(modifier: Modifier = Modifier) {
+            Box(modifier.element(TestElements.Foo).size(50.dp))
+        }
+
+        val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+        val scope =
+            rule.setContentAndCreateMainScope {
+                SceneTransitionLayout(state) {
+                    scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
+                    scene(SceneB) { Box(Modifier.fillMaxSize()) }
+                    scene(SceneC) {
+                        Box(Modifier.fillMaxSize()) { Foo(Modifier.offset(x = 100.dp, y = 100.dp)) }
+                    }
+                }
+            }
+
+        // During A => B, Foo disappears and stays in its original position.
+        scope.launch { state.startTransition(transition(SceneA, SceneB)) }
+        rule
+            .onNode(isElement(TestElements.Foo))
+            .assertSizeIsEqualTo(50.dp)
+            .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+
+        // Interrupt A => B by B => C.
+        var interruptionProgress by mutableFloatStateOf(1f)
+        scope.launch {
+            state.startTransition(
+                transition(SceneB, SceneC, interruptionProgress = { interruptionProgress })
+            )
+        }
+
+        // During B => C, Foo appears again. It is still at (0, 0) when the interruption progress is
+        // 100%, and converges to its position (100, 100) in C.
+        rule
+            .onNode(isElement(TestElements.Foo))
+            .assertSizeIsEqualTo(50.dp)
+            .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+
+        interruptionProgress = 0.5f
+        rule
+            .onNode(isElement(TestElements.Foo))
+            .assertSizeIsEqualTo(50.dp)
+            .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+
+        interruptionProgress = 0f
+        rule
+            .onNode(isElement(TestElements.Foo))
+            .assertSizeIsEqualTo(50.dp)
+            .assertPositionInRootIsEqualTo(100.dp, 100.dp)
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index 160865d..81d3f72 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -31,11 +31,8 @@
 import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.AuthenticationFlags
-import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -243,16 +240,11 @@
         }
 
     @Test
-    fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleepInAod() =
+    fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleep() =
         testScope.runTest {
             val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
             assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
 
-            kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.AOD,
-                testScope = this,
-            )
             kosmos.powerInteractor.setAsleepForTest()
             runCurrent()
 
@@ -266,26 +258,6 @@
         }
 
     @Test
-    fun deviceUnlockStatus_staysLocked_whenFingerprintUnlocked_whileDeviceAsleep() =
-        testScope.runTest {
-            val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
-            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
-            assertThat(kosmos.keyguardTransitionInteractor.getCurrentState())
-                .isEqualTo(KeyguardState.LOCKSCREEN)
-
-            kosmos.powerInteractor.setAsleepForTest()
-            runCurrent()
-
-            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
-
-            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
-                SuccessFingerprintAuthenticationStatus(0, true)
-            )
-            runCurrent()
-            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
-        }
-
-    @Test
     fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
         testScope.runTest {
             kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
index dda9cd5..4dcbdfa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
@@ -104,67 +104,6 @@
             }
         }
 
-    @Test
-    fun showLabels_updatesFromSharedPreferences() =
-        with(kosmos) {
-            testScope.runTest {
-                val latest by collectLastValue(underTest.showLabels)
-                assertThat(latest).isFalse()
-
-                setShowLabelsInSharedPreferences(true)
-                assertThat(latest).isTrue()
-
-                setShowLabelsInSharedPreferences(false)
-                assertThat(latest).isFalse()
-            }
-        }
-
-    @Test
-    fun showLabels_updatesFromUserChange() =
-        with(kosmos) {
-            testScope.runTest {
-                fakeUserRepository.setUserInfos(USERS)
-                val latest by collectLastValue(underTest.showLabels)
-
-                fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
-                setShowLabelsInSharedPreferences(false)
-
-                fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
-                setShowLabelsInSharedPreferences(true)
-
-                fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
-                assertThat(latest).isFalse()
-            }
-        }
-
-    @Test
-    fun setShowLabels_inSharedPreferences() {
-        underTest.setShowLabels(false)
-        assertThat(getShowLabelsFromSharedPreferences(true)).isFalse()
-
-        underTest.setShowLabels(true)
-        assertThat(getShowLabelsFromSharedPreferences(false)).isTrue()
-    }
-
-    @Test
-    fun setShowLabels_forDifferentUser() =
-        with(kosmos) {
-            testScope.runTest {
-                fakeUserRepository.setUserInfos(USERS)
-
-                fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
-                underTest.setShowLabels(false)
-                assertThat(getShowLabelsFromSharedPreferences(true)).isFalse()
-
-                fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
-                underTest.setShowLabels(true)
-                assertThat(getShowLabelsFromSharedPreferences(false)).isTrue()
-
-                fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
-                assertThat(getShowLabelsFromSharedPreferences(true)).isFalse()
-            }
-        }
-
     private fun getSharedPreferences(): SharedPreferences =
         with(kosmos) {
             return userFileManager.getSharedPreferences(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 55f88cc..08b9961 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -753,7 +753,7 @@
                 lastSleepReason = WakeSleepReason.POWER_BUTTON,
                 powerButtonLaunchGestureTriggered = false,
             )
-            transitionStateFlow.value = Transition(from = Scenes.Shade, to = Scenes.Lockscreen)
+            transitionStateFlow.value = Transition(from = Scenes.Gone, to = Scenes.Lockscreen)
             assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
 
             kosmos.fakePowerRepository.updateWakefulness(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt
new file mode 100644
index 0000000..a9a5cac
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.data.repository
+
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.commandline.commandRegistry
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadePositionRepositoryTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val commandRegistry = kosmos.commandRegistry
+    private val pw = PrintWriter(StringWriter())
+
+    private val underTest = ShadePositionRepositoryImpl(commandRegistry)
+
+    @Before
+    fun setUp() {
+        underTest.start()
+    }
+
+    @Test
+    fun commandDisplayOverride_updatesDisplayId() =
+        testScope.runTest {
+            val displayId by collectLastValue(underTest.displayId)
+            assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+
+            val newDisplayId = 2
+            commandRegistry.onShellCommand(
+                pw,
+                arrayOf("shade_display_override", newDisplayId.toString()),
+            )
+
+            assertThat(displayId).isEqualTo(newDisplayId)
+        }
+
+    @Test
+    fun commandShadeDisplayOverride_resetsDisplayId() =
+        testScope.runTest {
+            val displayId by collectLastValue(underTest.displayId)
+            assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+
+            val newDisplayId = 2
+            commandRegistry.onShellCommand(
+                pw,
+                arrayOf("shade_display_override", newDisplayId.toString()),
+            )
+            assertThat(displayId).isEqualTo(newDisplayId)
+
+            commandRegistry.onShellCommand(pw, arrayOf("shade_display_override", "reset"))
+            assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
index 643acdb..2a3878c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
@@ -16,18 +16,22 @@
 
 package com.android.systemui.statusbar.connectivity
 
+import android.content.Context
+import android.os.UserHandle
 import android.os.UserManager
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
+import android.platform.test.annotations.EnableFlags
 import android.testing.TestableLooper.RunWithLooper
 import androidx.lifecycle.Lifecycle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.settings.UserTracker
-import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.wifitrackerlib.WifiEntry
 import com.android.wifitrackerlib.WifiPickerTracker
 import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -35,36 +39,28 @@
 import org.mockito.ArgumentMatchers.anyList
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.never
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
 class AccessPointControllerImplTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var userManager: UserManager
-    @Mock
-    private lateinit var userTracker: UserTracker
-    @Mock
-    private lateinit var wifiPickerTrackerFactory:
-            WifiPickerTrackerFactory
-    @Mock
-    private lateinit var wifiPickerTracker: WifiPickerTracker
-    @Mock
-    private lateinit var callback: AccessPointController.AccessPointCallback
-    @Mock
-    private lateinit var otherCallback: AccessPointController.AccessPointCallback
-    @Mock
-    private lateinit var wifiEntryConnected: WifiEntry
-    @Mock
-    private lateinit var wifiEntryOther: WifiEntry
-    @Captor
-    private lateinit var wifiEntryListCaptor: ArgumentCaptor<List<WifiEntry>>
+    @Mock private lateinit var userManager: UserManager
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var wifiPickerTrackerFactory: WifiPickerTrackerFactory
+    @Mock private lateinit var wifiPickerTracker: WifiPickerTracker
+    @Mock private lateinit var callback: AccessPointController.AccessPointCallback
+    @Mock private lateinit var otherCallback: AccessPointController.AccessPointCallback
+    @Mock private lateinit var wifiEntryConnected: WifiEntry
+    @Mock private lateinit var wifiEntryOther: WifiEntry
+    @Captor private lateinit var wifiEntryListCaptor: ArgumentCaptor<List<WifiEntry>>
 
     private val instantExecutor = Executor { it.run() }
     private lateinit var controller: AccessPointControllerImpl
@@ -72,19 +68,21 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        `when`(wifiPickerTrackerFactory.create(any(), any(), any())).thenReturn(wifiPickerTracker)
+        `when`(wifiPickerTrackerFactory.create(any(), any(), any(), any()))
+            .thenReturn(wifiPickerTracker)
 
         `when`(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntryConnected)
-        `when`(wifiPickerTracker.wifiEntries).thenReturn(ArrayList<WifiEntry>().apply {
-            add(wifiEntryOther)
-        })
+        `when`(wifiPickerTracker.wifiEntries)
+            .thenReturn(ArrayList<WifiEntry>().apply { add(wifiEntryOther) })
 
-        controller = AccessPointControllerImpl(
+        controller =
+            AccessPointControllerImpl(
+                mContext,
                 userManager,
                 userTracker,
                 instantExecutor,
-                wifiPickerTrackerFactory
-        )
+                wifiPickerTrackerFactory,
+            )
 
         controller.init()
     }
@@ -183,13 +181,15 @@
 
     @Test
     fun testReturnEmptyListWhenNoWifiPickerTracker() {
-        `when`(wifiPickerTrackerFactory.create(any(), any(), any())).thenReturn(null)
-        val otherController = AccessPointControllerImpl(
+        `when`(wifiPickerTrackerFactory.create(any(), any(), any(), any())).thenReturn(null)
+        val otherController =
+            AccessPointControllerImpl(
+                mContext,
                 userManager,
                 userTracker,
                 instantExecutor,
-                wifiPickerTrackerFactory
-        )
+                wifiPickerTrackerFactory,
+            )
         otherController.init()
 
         otherController.addAccessPointCallback(callback)
@@ -244,4 +244,19 @@
         verify(wifiEntryOther).connect(any())
         verify(callback, never()).onSettingsActivityTriggered(any())
     }
+
+    @Test
+    @EnableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
+    fun switchUsers() {
+        val primaryUserMockContext = mock<Context>()
+        mContext.prepareCreateContextAsUser(UserHandle.of(PRIMARY_USER_ID), primaryUserMockContext)
+        controller.onUserSwitched(PRIMARY_USER_ID)
+        // Create is expected to be called once when the test starts and a second time when the user
+        // is switched.
+        verify(wifiPickerTrackerFactory, times(2)).create(any(), any(), any(), any())
+    }
+
+    private companion object {
+        private const val PRIMARY_USER_ID = 1
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
index dae5542..50db9f7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
@@ -21,16 +21,19 @@
 import android.view.View.VISIBLE
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.nullable
@@ -52,8 +55,11 @@
     private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
     @Mock private lateinit var mediaDataManager: MediaDataManager
     @Mock private lateinit var stackLayout: NotificationStackScrollLayout
+    @Mock private lateinit var seenNotificationsInteractor: SeenNotificationsInteractor
 
     private val testableResources = mContext.orCreateTestableResources
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
 
     private lateinit var sizeCalculator: NotificationStackSizeCalculator
 
@@ -72,7 +78,9 @@
                 lockscreenShadeTransitionController = lockscreenShadeTransitionController,
                 mediaDataManager = mediaDataManager,
                 testableResources.resources,
-                    ResourcesSplitShadeStateController()
+                ResourcesSplitShadeStateController(),
+                seenNotificationsInteractor = seenNotificationsInteractor,
+                scope = testScope,
             )
     }
 
@@ -85,7 +93,7 @@
                 rows,
                 spaceForNotifications = 0f,
                 spaceForShelf = 0f,
-                shelfHeight = 0f
+                shelfHeight = 0f,
             )
 
         assertThat(maxNotifications).isEqualTo(0)
@@ -101,7 +109,7 @@
                 rows,
                 spaceForNotifications = Float.MAX_VALUE,
                 spaceForShelf = Float.MAX_VALUE,
-                shelfHeight
+                shelfHeight,
             )
 
         assertThat(maxNotifications).isEqualTo(numberOfRows)
@@ -137,7 +145,7 @@
                 listOf(row),
                 /* spaceForNotifications= */ 5f,
                 /* spaceForShelf= */ 0f,
-                /* shelfHeight= */ 0f
+                /* shelfHeight= */ 0f,
             )
 
         assertThat(maxNotifications).isEqualTo(1)
@@ -148,11 +156,7 @@
         setGapHeight(gapHeight)
         val shelfHeight = shelfHeight + dividerHeight
         val spaceForNotifications =
-            listOf(
-                    rowHeight + dividerHeight,
-                    gapHeight + rowHeight + dividerHeight,
-                )
-                .sum()
+            listOf(rowHeight + dividerHeight, gapHeight + rowHeight + dividerHeight).sum()
         val spaceForShelf = gapHeight + dividerHeight + shelfHeight
         val rows =
             listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight))
@@ -162,7 +166,7 @@
                 rows,
                 spaceForNotifications + 1,
                 spaceForShelf,
-                shelfHeight
+                shelfHeight,
             )
 
         assertThat(maxNotifications).isEqualTo(2)
@@ -173,12 +177,7 @@
         // Each row in separate section.
         setGapHeight(gapHeight)
 
-        val notifSpace =
-            listOf(
-                    rowHeight,
-                    dividerHeight + gapHeight + rowHeight,
-                )
-                .sum()
+        val notifSpace = listOf(rowHeight, dividerHeight + gapHeight + rowHeight).sum()
 
         val shelfSpace = dividerHeight + gapHeight + shelfHeight
         val spaceUsed = notifSpace + shelfSpace
@@ -209,7 +208,7 @@
                 rows,
                 spaceForNotifications + 1,
                 spaceForShelf,
-                shelfHeight
+                shelfHeight,
             )
         assertThat(maxNotifications).isEqualTo(1)
 
@@ -252,7 +251,7 @@
                 visibleIndex = 0,
                 previousView = null,
                 stack = stackLayout,
-                onLockscreen = true
+                onLockscreen = true,
             )
         assertThat(space.whenEnoughSpace).isEqualTo(10f)
     }
@@ -272,7 +271,7 @@
                 visibleIndex = 0,
                 previousView = null,
                 stack = stackLayout,
-                onLockscreen = true
+                onLockscreen = true,
             )
         assertThat(space.whenEnoughSpace).isEqualTo(5)
     }
@@ -291,7 +290,7 @@
                 visibleIndex = 0,
                 previousView = null,
                 stack = stackLayout,
-                onLockscreen = true
+                onLockscreen = true,
             )
         assertThat(space.whenSavingSpace).isEqualTo(5)
     }
@@ -311,7 +310,7 @@
                 visibleIndex = 0,
                 previousView = null,
                 stack = stackLayout,
-                onLockscreen = true
+                onLockscreen = true,
             )
         assertThat(space.whenSavingSpace).isEqualTo(5)
     }
@@ -330,7 +329,7 @@
                 visibleIndex = 0,
                 previousView = null,
                 stack = stackLayout,
-                onLockscreen = false
+                onLockscreen = false,
             )
         assertThat(space.whenEnoughSpace).isEqualTo(rowHeight)
         assertThat(space.whenSavingSpace).isEqualTo(rowHeight)
@@ -340,14 +339,14 @@
         rows: List<ExpandableView>,
         spaceForNotifications: Float,
         spaceForShelf: Float,
-        shelfHeight: Float = this.shelfHeight
+        shelfHeight: Float = this.shelfHeight,
     ): Int {
         setupChildren(rows)
         return sizeCalculator.computeMaxKeyguardNotifications(
             stackLayout,
             spaceForNotifications,
             spaceForShelf,
-            shelfHeight
+            shelfHeight,
         )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
index b5dbc3f..33223ae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
+import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.kotlinArgumentCaptor
@@ -71,6 +72,7 @@
     private val demoModelFlow = MutableStateFlow<FakeWifiEventModel?>(null)
 
     private val mainExecutor = FakeExecutor(FakeSystemClock())
+    private val userRepository = FakeUserRepository()
 
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
@@ -82,10 +84,13 @@
         // Never start in demo mode
         whenever(demoModeController.isInDemoMode).thenReturn(false)
 
-        whenever(wifiPickerTrackerFactory.create(any(), any(), any())).thenReturn(wifiPickerTracker)
+        whenever(wifiPickerTrackerFactory.create(any(), any(), any(), any()))
+            .thenReturn(wifiPickerTracker)
 
         realImpl =
             WifiRepositoryImpl(
+                mContext,
+                userRepository,
                 testScope.backgroundScope,
                 mainExecutor,
                 testDispatcher,
@@ -97,11 +102,7 @@
 
         whenever(demoModeWifiDataSource.wifiEvents).thenReturn(demoModelFlow)
 
-        demoImpl =
-            DemoWifiRepository(
-                demoModeWifiDataSource,
-                testScope.backgroundScope,
-            )
+        demoImpl = DemoWifiRepository(demoModeWifiDataSource, testScope.backgroundScope)
 
         underTest =
             WifiRepositorySwitcher(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index 9b47ead..8a45930 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.res.R
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.model.SelectedUserModel
@@ -74,6 +75,10 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         tracker = FakeUserTracker()
+        context.orCreateTestableResources.addOverride(
+            R.bool.config_userSwitchingMustGoThroughLoginScreen,
+            false,
+        )
     }
 
     @Test
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index f825459..a107322 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
 import com.android.systemui.keyguard.domain.backup.KeyguardQuickAffordanceBackupHelper
 import com.android.systemui.people.widget.PeopleBackupHelper
+import com.android.systemui.qs.panels.domain.backup.QSPreferencesBackupHelper
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserFileManagerImpl
 
@@ -58,9 +59,9 @@
         private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
         private const val KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY =
             "systemui.keyguard.quickaffordance.shared_preferences"
-        private const val COMMUNAL_PREFS_BACKUP_KEY =
-            "systemui.communal.shared_preferences"
+        private const val COMMUNAL_PREFS_BACKUP_KEY = "systemui.communal.shared_preferences"
         private const val COMMUNAL_STATE_BACKUP_KEY = "systemui.communal_state"
+        private const val QS_PREFERENCES_BACKUP_KEY = "systemui.qs.shared_preferences"
         val controlsDataLock = Any()
         const val ACTION_RESTORE_FINISHED = "com.android.systemui.backup.RESTORE_FINISHED"
         const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
@@ -74,22 +75,20 @@
         val keys = PeopleBackupHelper.getFilesToBackup()
         addHelper(
             PEOPLE_TILES_BACKUP_KEY,
-            PeopleBackupHelper(this, userHandle, keys.toTypedArray())
+            PeopleBackupHelper(this, userHandle, keys.toTypedArray()),
         )
         addHelper(
             KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY,
-            KeyguardQuickAffordanceBackupHelper(
-                context = this,
-                userId = userHandle.identifier,
-            ),
+            KeyguardQuickAffordanceBackupHelper(context = this, userId = userHandle.identifier),
+        )
+        addHelper(
+            QS_PREFERENCES_BACKUP_KEY,
+            QSPreferencesBackupHelper(context = this, userId = userHandle.identifier),
         )
         if (communalEnabled()) {
             addHelper(
                 COMMUNAL_PREFS_BACKUP_KEY,
-                CommunalPrefsBackupHelper(
-                    context = this,
-                    userId = userHandle.identifier,
-                )
+                CommunalPrefsBackupHelper(context = this, userId = userHandle.identifier),
             )
             addHelper(
                 COMMUNAL_STATE_BACKUP_KEY,
@@ -110,10 +109,7 @@
     }
 
     private fun addControlsHelper(userId: Int) {
-        val file = UserFileManagerImpl.createFile(
-            userId = userId,
-            fileName = CONTROLS,
-        )
+        val file = UserFileManagerImpl.createFile(userId = userId, fileName = CONTROLS)
         // The map in mapOf is guaranteed to be order preserving
         val controlsMap = mapOf(file.getPath() to getPPControlsFile(this, userId))
         NoOverwriteFileBackupHelper(controlsDataLock, this, controlsMap).also {
@@ -134,6 +130,7 @@
      * @property lock a lock to hold while backing up and restoring the files.
      * @property context the context of the [BackupAgent]
      * @property fileNamesAndPostProcess a map from the filenames to back up and the post processing
+     *
      * ```
      *                                   actions to take
      * ```
@@ -141,7 +138,7 @@
     private class NoOverwriteFileBackupHelper(
         val lock: Any,
         val context: Context,
-        val fileNamesAndPostProcess: Map<String, () -> Unit>
+        val fileNamesAndPostProcess: Map<String, () -> Unit>,
     ) : FileBackupHelper(context, *fileNamesAndPostProcess.keys.toTypedArray()) {
 
         override fun restoreEntity(data: BackupDataInputStream) {
@@ -152,11 +149,12 @@
                 return
             }
             synchronized(lock) {
-                traceSection("File restore: ${data.key}") {
-                    super.restoreEntity(data)
-                }
-                Log.d(TAG, "Finishing restore for ${data.key} for user ${context.userId}. " +
-                        "Starting postProcess.")
+                traceSection("File restore: ${data.key}") { super.restoreEntity(data) }
+                Log.d(
+                    TAG,
+                    "Finishing restore for ${data.key} for user ${context.userId}. " +
+                        "Starting postProcess.",
+                )
                 traceSection("Postprocess: ${data.key}") {
                     fileNamesAndPostProcess.get(data.key)?.invoke()
                 }
@@ -167,7 +165,7 @@
         override fun performBackup(
             oldState: ParcelFileDescriptor?,
             data: BackupDataOutput?,
-            newState: ParcelFileDescriptor?
+            newState: ParcelFileDescriptor?,
         ) {
             synchronized(lock) { super.performBackup(oldState, data, newState) }
         }
@@ -176,15 +174,13 @@
 
 private fun getPPControlsFile(context: Context, userId: Int): () -> Unit {
     return {
-        val file = UserFileManagerImpl.createFile(
-            userId = userId,
-            fileName = BackupHelper.CONTROLS,
-        )
+        val file = UserFileManagerImpl.createFile(userId = userId, fileName = BackupHelper.CONTROLS)
         if (file.exists()) {
-            val dest = UserFileManagerImpl.createFile(
-                userId = userId,
-                fileName = AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME,
-            )
+            val dest =
+                UserFileManagerImpl.createFile(
+                    userId = userId,
+                    fileName = AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME,
+                )
             file.copyTo(dest)
             val jobScheduler = context.getSystemService(JobScheduler::class.java)
             jobScheduler?.schedule(
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
index c464a66..6c335e7 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
@@ -18,13 +18,18 @@
 
 import com.android.keyguard.EmptyLockIconViewController
 import com.android.keyguard.LockIconViewController
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
 import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import dagger.Binds
 import dagger.Lazy
 import dagger.Module
 import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 import dagger.multibindings.Multibinds
 
 @Module(includes = [DeviceEntryRepositoryModule::class, FaceWakeUpTriggersConfigModule::class])
@@ -34,6 +39,13 @@
      */
     @Multibinds abstract fun deviceEntryIconTransitionSet(): Set<DeviceEntryIconTransition>
 
+    @Binds
+    @IntoMap
+    @ClassKey(DeviceUnlockedInteractor.Activator::class)
+    abstract fun deviceUnlockedInteractorActivator(
+        activator: DeviceUnlockedInteractor.Activator
+    ): CoreStartable
+
     companion object {
         @Provides
         @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index 3f937bb..675f00a 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -5,6 +5,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.user.data.repository.UserRepository
 import dagger.Binds
@@ -42,6 +43,8 @@
      */
     val isBypassEnabled: StateFlow<Boolean>
 
+    val deviceUnlockStatus: MutableStateFlow<DeviceUnlockStatus>
+
     /**
      * Whether the lockscreen is enabled for the current user. This is `true` whenever the user has
      * chosen any secure authentication method and even if they set the lockscreen to be dismissed
@@ -84,6 +87,9 @@
                 initialValue = keyguardBypassController.bypassEnabled,
             )
 
+    override val deviceUnlockStatus =
+        MutableStateFlow(DeviceUnlockStatus(isUnlocked = false, deviceUnlockSource = null))
+
     override suspend fun isLockscreenEnabled(): Boolean {
         return withContext(backgroundDispatcher) {
             val selectedUserId = userRepository.getSelectedUserInfo().id
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index 5259c5d..24278ec 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.deviceentry.domain.interactor
 
+import android.util.Log
 import androidx.annotation.VisibleForTesting
+import com.android.systemui.CoreStartable
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.dagger.SysUISingleton
@@ -26,42 +28,40 @@
 import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
 import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
 import com.android.systemui.flags.SystemPropertiesHelper
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.TrustInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class DeviceUnlockedInteractor
 @Inject
 constructor(
-    @Application private val applicationScope: CoroutineScope,
-    authenticationInteractor: AuthenticationInteractor,
-    deviceEntryRepository: DeviceEntryRepository,
+    private val authenticationInteractor: AuthenticationInteractor,
+    private val repository: DeviceEntryRepository,
     trustInteractor: TrustInteractor,
     faceAuthInteractor: DeviceEntryFaceAuthInteractor,
     fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
     private val powerInteractor: PowerInteractor,
     private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
     private val systemPropertiesHelper: SystemPropertiesHelper,
-    keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) {
+) : ExclusiveActivatable() {
 
     private val deviceUnlockSource =
         merge(
@@ -69,7 +69,7 @@
             faceAuthInteractor.isAuthenticated
                 .filter { it }
                 .map {
-                    if (deviceEntryRepository.isBypassEnabled.value) {
+                    if (repository.isBypassEnabled.value) {
                         DeviceUnlockSource.FaceWithBypass
                     } else {
                         DeviceUnlockSource.FaceWithoutBypass
@@ -163,43 +163,59 @@
      * proceed.
      */
     val deviceUnlockStatus: StateFlow<DeviceUnlockStatus> =
-        authenticationInteractor.authenticationMethod
-            .flatMapLatest { authMethod ->
-                if (!authMethod.isSecure) {
-                    flowOf(DeviceUnlockStatus(true, null))
-                } else if (authMethod == AuthenticationMethodModel.Sim) {
-                    // Device is locked if SIM is locked.
-                    flowOf(DeviceUnlockStatus(false, null))
-                } else {
-                    combine(
-                            powerInteractor.isAsleep,
-                            isInLockdown,
-                            keyguardTransitionInteractor
-                                .transitionValue(KeyguardState.AOD)
-                                .map { it == 1f }
-                                .distinctUntilChanged(),
-                            ::Triple,
-                        )
-                        .flatMapLatestConflated { (isAsleep, isInLockdown, isAod) ->
-                            val isForceLocked =
-                                when {
-                                    isAsleep && !isAod -> true
-                                    isInLockdown -> true
-                                    else -> false
-                                }
-                            if (isForceLocked) {
-                                flowOf(DeviceUnlockStatus(false, null))
-                            } else {
-                                deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
+        repository.deviceUnlockStatus.asStateFlow()
+
+    override suspend fun onActivated(): Nothing {
+        authenticationInteractor.authenticationMethod.collectLatest { authMethod ->
+            if (!authMethod.isSecure) {
+                // Device remains unlocked as long as the authentication method is not secure.
+                Log.d(TAG, "remaining unlocked because auth method not secure")
+                repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, null)
+            } else if (authMethod == AuthenticationMethodModel.Sim) {
+                // Device remains locked while SIM is locked.
+                Log.d(TAG, "remaining locked because SIM locked")
+                repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null)
+            } else {
+                try {
+                    Log.d(TAG, "started watching for lock and unlock events")
+                    coroutineScope {
+                        launch {
+                            // Unlock the device when a new unlock source is detected.
+                            deviceUnlockSource.collect {
+                                Log.d(TAG, "unlocking due to \"$it\"")
+                                repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, it)
                             }
                         }
+
+                        launch {
+                            // Lock events.
+                            merge(
+                                    // Device goes to sleep.
+                                    powerInteractor.isAsleep
+                                        .distinctUntilChanged()
+                                        .filter { it }
+                                        .map { "asleep" },
+                                    // Device enters lockdown.
+                                    isInLockdown
+                                        .distinctUntilChanged()
+                                        .filter { it }
+                                        .map { "lockdown" },
+                                )
+                                .collect { reason: String ->
+                                    Log.d(TAG, "locking due to \"$reason\"")
+                                    repository.deviceUnlockStatus.value =
+                                        DeviceUnlockStatus(false, null)
+                                }
+                        }
+                    }
+                } finally {
+                    Log.d(TAG, "stopped watching for lock and unlock events")
                 }
             }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.Eagerly,
-                initialValue = DeviceUnlockStatus(false, null),
-            )
+        }
+
+        awaitCancellation()
+    }
 
     private fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean {
         return when (this) {
@@ -226,7 +242,20 @@
         return systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
     }
 
+    /** [CoreStartable] that activates the [DeviceUnlockedInteractor]. */
+    class Activator
+    @Inject
+    constructor(
+        @Application private val applicationScope: CoroutineScope,
+        private val interactor: DeviceUnlockedInteractor,
+    ) : CoreStartable {
+        override fun start() {
+            applicationScope.launch { interactor.activate() }
+        }
+    }
+
     companion object {
+        private val TAG = "DeviceUnlockedInteractor"
         @VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
         @VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
index 971598d..b0c6073 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
@@ -17,9 +17,16 @@
 package com.android.systemui.qs.panels.data.repository
 
 import android.content.Context
+import android.content.IntentFilter
 import android.content.SharedPreferences
+import com.android.systemui.backup.BackupHelper
+import com.android.systemui.backup.BackupHelper.Companion.ACTION_RESTORE_FINISHED
+import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.qs.panels.shared.model.PanelsLog
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.user.data.repository.UserRepository
@@ -29,9 +36,11 @@
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 
 /** Repository for QS user preferences. */
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -43,26 +52,31 @@
     private val userRepository: UserRepository,
     private val defaultLargeTilesRepository: DefaultLargeTilesRepository,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
+    @PanelsLog private val logBuffer: LogBuffer,
+    broadcastDispatcher: BroadcastDispatcher,
 ) {
-    /** Whether to show the labels on icon tiles for the current user. */
-    val showLabels: Flow<Boolean> =
-        userRepository.selectedUserInfo
-            .flatMapLatest { userInfo ->
-                val prefs = getSharedPrefs(userInfo.id)
-                prefs.observe().emitOnStart().map { prefs.getBoolean(ICON_LABELS_KEY, false) }
-            }
-            .flowOn(backgroundDispatcher)
+    private val logger by lazy { Logger(logBuffer, TAG) }
+
+    private val backupRestorationEvents: Flow<Unit> =
+        broadcastDispatcher
+            .broadcastFlow(
+                filter = IntentFilter(ACTION_RESTORE_FINISHED),
+                flags = Context.RECEIVER_NOT_EXPORTED,
+                permission = BackupHelper.PERMISSION_SELF,
+            )
+            .onEach { logger.i("Restored state for QS preferences.") }
+            .emitOnStart()
 
     /** Set of [TileSpec] to display as large tiles for the current user. */
     val largeTilesSpecs: Flow<Set<TileSpec>> =
-        userRepository.selectedUserInfo
-            .flatMapLatest { userInfo ->
+        combine(backupRestorationEvents, userRepository.selectedUserInfo, ::Pair)
+            .flatMapLatest { (_, userInfo) ->
                 val prefs = getSharedPrefs(userInfo.id)
                 prefs.observe().emitOnStart().map {
                     prefs
                         .getStringSet(
                             LARGE_TILES_SPECS_KEY,
-                            defaultLargeTilesRepository.defaultLargeTiles.map { it.spec }.toSet()
+                            defaultLargeTilesRepository.defaultLargeTiles.map { it.spec }.toSet(),
                         )
                         ?.map { TileSpec.create(it) }
                         ?.toSet() ?: defaultLargeTilesRepository.defaultLargeTiles
@@ -70,13 +84,6 @@
             }
             .flowOn(backgroundDispatcher)
 
-    /** Sets for the current user whether to show the labels on icon tiles. */
-    fun setShowLabels(showLabels: Boolean) {
-        with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) {
-            edit().putBoolean(ICON_LABELS_KEY, showLabels).apply()
-        }
-    }
-
     /** Sets for the current user the set of [TileSpec] to display as large tiles. */
     fun setLargeTilesSpecs(specs: Set<TileSpec>) {
         with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) {
@@ -85,15 +92,11 @@
     }
 
     private fun getSharedPrefs(userId: Int): SharedPreferences {
-        return userFileManager.getSharedPreferences(
-            FILE_NAME,
-            Context.MODE_PRIVATE,
-            userId,
-        )
+        return userFileManager.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, userId)
     }
 
     companion object {
-        private const val ICON_LABELS_KEY = "show_icon_labels"
+        private const val TAG = "QSPreferencesRepository"
         private const val LARGE_TILES_SPECS_KEY = "large_tiles_specs"
         const val FILE_NAME = "quick_settings_prefs"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/backup/QSPreferencesBackupHelper.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/backup/QSPreferencesBackupHelper.kt
new file mode 100644
index 0000000..2c51ca9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/backup/QSPreferencesBackupHelper.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.domain.backup
+
+import android.app.backup.SharedPreferencesBackupHelper
+import android.content.Context
+import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository.Companion.FILE_NAME
+import com.android.systemui.settings.UserFileManagerImpl
+
+class QSPreferencesBackupHelper(context: Context, userId: Int) :
+    SharedPreferencesBackupHelper(
+        context,
+        UserFileManagerImpl.createFile(userId = userId, fileName = FILE_NAME).path,
+    )
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index 42d4eff..63510b8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -20,6 +20,7 @@
 import android.content.res.Resources
 import android.view.LayoutInflater
 import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+import com.android.systemui.CoreStartable
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.common.ui.ConfigurationStateImpl
 import com.android.systemui.common.ui.GlobalConfig
@@ -29,12 +30,16 @@
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.ShadePositionRepository
+import com.android.systemui.shade.data.repository.ShadePositionRepositoryImpl
 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
 import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
 import com.android.systemui.statusbar.phone.ConfigurationForwarder
 import com.android.systemui.statusbar.policy.ConfigurationController
 import dagger.Module
 import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 
 /**
  * Module responsible for managing display-specific components and resources for the notification
@@ -149,4 +154,25 @@
             configurationInteractor
         }
     }
+
+    @SysUISingleton
+    @Provides
+    @ShadeDisplayAware
+    fun provideShadePositionRepository(impl: ShadePositionRepositoryImpl): ShadePositionRepository {
+        ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
+        return impl
+    }
+
+    @Provides
+    @IntoMap
+    @ClassKey(ShadePositionRepositoryImpl::class)
+    fun provideShadePositionRepositoryAsCoreStartable(
+        impl: ShadePositionRepositoryImpl
+    ): CoreStartable {
+        return if (ShadeWindowGoesAround.isEnabled) {
+            impl
+        } else {
+            CoreStartable.NOP
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
new file mode 100644
index 0000000..802fc0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.shade
+
+import android.view.Display
+import com.android.systemui.shade.data.repository.ShadePositionRepository
+import com.android.systemui.statusbar.commandline.Command
+import java.io.PrintWriter
+
+class ShadePrimaryDisplayCommand(private val positionRepository: ShadePositionRepository) :
+    Command {
+
+    override fun execute(pw: PrintWriter, args: List<String>) {
+        if (args[0].lowercase() == "reset") {
+            positionRepository.resetDisplayId()
+            pw.println("Reset shade primary display id to ${Display.DEFAULT_DISPLAY}")
+            return
+        }
+
+        val displayId: Int =
+            try {
+                args[0].toInt()
+            } catch (e: NumberFormatException) {
+                pw.println("Error: task id should be an integer")
+                return
+            }
+
+        if (displayId < 0) {
+            pw.println("Error: display id should be positive integer")
+        }
+
+        positionRepository.setDisplayId(displayId)
+        pw.println("New shade primary display id is $displayId")
+    }
+
+    override fun help(pw: PrintWriter) {
+        pw.println("shade_display_override <displayId> ")
+        pw.println("Set the display which is holding the shade.")
+        pw.println("shade_display_override reset ")
+        pw.println("Reset the display which is holding the shade.")
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt
new file mode 100644
index 0000000..24c067a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.shade.data.repository
+
+import android.view.Display
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.ShadePrimaryDisplayCommand
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+interface ShadePositionRepository {
+    /** ID of the display which currently hosts the shade */
+    val displayId: StateFlow<Int>
+
+    /**
+     * Updates the value of the shade display id stored, emitting to the new display id to every
+     * component dependent on the shade display id
+     */
+    fun setDisplayId(displayId: Int)
+
+    /** Resets value of shade primary display to the default display */
+    fun resetDisplayId()
+}
+
+/** Source of truth for the display currently holding the shade. */
+@SysUISingleton
+class ShadePositionRepositoryImpl
+@Inject
+constructor(private val commandRegistry: CommandRegistry) : ShadePositionRepository, CoreStartable {
+    private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
+
+    override val displayId: StateFlow<Int>
+        get() = _displayId
+
+    override fun setDisplayId(displayId: Int) {
+        _displayId.value = displayId
+    }
+
+    override fun resetDisplayId() {
+        _displayId.value = Display.DEFAULT_DISPLAY
+    }
+
+    override fun start() {
+        commandRegistry.registerCommand("shade_display_override") {
+            ShadePrimaryDisplayCommand(this)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
index 3a31851..52a79d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar.connectivity;
 
+import static com.android.systemui.Flags.multiuserWifiPickerTrackerSupport;
+
+import android.content.Context;
 import android.content.Intent;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -65,13 +68,16 @@
     private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
 
     private int mCurrentUser;
+    private Context mContext;
 
     public AccessPointControllerImpl(
+            Context context,
             UserManager userManager,
             UserTracker userTracker,
             Executor mainExecutor,
             WifiPickerTrackerFactory wifiPickerTrackerFactory
     ) {
+        mContext = context;
         mUserManager = userManager;
         mUserTracker = userTracker;
         mCurrentUser = userTracker.getUserId();
@@ -87,7 +93,11 @@
      */
     public void init() {
         if (mWifiPickerTracker == null) {
-            mWifiPickerTracker = mWifiPickerTrackerFactory.create(this.getLifecycle(), this, TAG);
+            // We are creating the WifiPickerTracker during init to make sure we have one
+            // available at all times however we expect this to be recreated very quickly
+            // with a user-specific context in onUserSwitched.
+            mWifiPickerTracker =
+                mWifiPickerTrackerFactory.create(mContext, this.getLifecycle(), this, TAG);
         }
     }
 
@@ -116,6 +126,19 @@
 
     void onUserSwitched(int newUserId) {
         mCurrentUser = newUserId;
+        // Return early if multiuser support is not enabled.
+        if (!multiuserWifiPickerTrackerSupport()) {
+            return;
+        }
+
+        if (mWifiPickerTracker != null) {
+            mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.CREATED));
+        }
+        Context context = mContext.createContextAsUser(UserHandle.of(newUserId), /* flags= */ 0);
+        mWifiPickerTracker = mWifiPickerTrackerFactory.create(context, mLifecycle, this, TAG);
+        if (!mCallbacks.isEmpty()) {
+            mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.STARTED));
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt
index dc2ebe5..947ce33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt
@@ -22,6 +22,7 @@
 import android.os.Handler
 import android.os.SimpleClock
 import androidx.lifecycle.Lifecycle
+import com.android.systemui.Flags.multiuserWifiPickerTrackerSupport
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.util.concurrency.ThreadFactory
@@ -41,7 +42,7 @@
 class WifiPickerTrackerFactory
 @Inject
 constructor(
-    private val context: Context,
+    private val applicationContext: Context,
     private val wifiManager: WifiManager?,
     private val connectivityManager: ConnectivityManager,
     private val systemClock: SystemClock,
@@ -64,16 +65,23 @@
      * @return a new [WifiPickerTracker] or null if [WifiManager] is null.
      */
     fun create(
+        userContext: Context,
         lifecycle: Lifecycle,
         listener: WifiPickerTrackerCallback,
         name: String,
     ): WifiPickerTracker? {
         return if (wifiManager == null) {
             null
-        } else
+        } else {
+            val contextToUse =
+                if (multiuserWifiPickerTrackerSupport()) {
+                    userContext
+                } else {
+                    applicationContext
+                }
             WifiPickerTracker(
                 lifecycle,
-                context,
+                contextToUse,
                 wifiManager,
                 connectivityManager,
                 mainHandler,
@@ -86,6 +94,7 @@
                 SCAN_INTERVAL_MILLIS,
                 listener,
             )
+        }
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
index 2fded34..e232849 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
@@ -19,6 +19,7 @@
 import android.annotation.SuppressLint
 import android.app.NotificationManager
 import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dump.DumpManager
@@ -50,7 +51,6 @@
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * If the setting is enabled, this will track seen notifications and ensure that they only show in
@@ -74,7 +74,7 @@
 
     private val unseenNotifications = mutableSetOf<NotificationEntry>()
     private var isShadeVisible = false
-    private var unseenFilterEnabled = false
+    private var minimalismEnabled = false
 
     override fun attach(pipeline: NotifPipeline) {
         if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) {
@@ -83,7 +83,7 @@
         pipeline.addPromoter(unseenNotifPromoter)
         pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotifs)
         pipeline.addCollectionListener(collectionListener)
-        scope.launch { trackUnseenFilterSettingChanges() }
+        scope.launch { trackLockScreenNotificationMinimalismSettingChanges() }
         dumpManager.registerDumpable(this)
     }
 
@@ -136,12 +136,12 @@
         return seenNotificationsInteractor.isLockScreenNotificationMinimalismEnabled()
     }
 
-    private suspend fun trackUnseenFilterSettingChanges() {
+    private suspend fun trackLockScreenNotificationMinimalismSettingChanges() {
         // Only filter the seen notifs when the lock screen minimalism feature settings is on.
         minimalismFeatureSettingEnabled().collectLatest { isMinimalismSettingEnabled ->
             // update local field and invalidate if necessary
-            if (isMinimalismSettingEnabled != unseenFilterEnabled) {
-                unseenFilterEnabled = isMinimalismSettingEnabled
+            if (isMinimalismSettingEnabled != minimalismEnabled) {
+                minimalismEnabled = isMinimalismSettingEnabled
                 unseenNotifications.clear()
                 unseenNotifPromoter.invalidateList("unseen setting changed")
             }
@@ -156,21 +156,21 @@
     private val collectionListener =
         object : NotifCollectionListener {
             override fun onEntryAdded(entry: NotificationEntry) {
-                if (unseenFilterEnabled && !isShadeVisible) {
+                if (minimalismEnabled && !isShadeVisible) {
                     logger.logUnseenAdded(entry.key)
                     unseenNotifications.add(entry)
                 }
             }
 
             override fun onEntryUpdated(entry: NotificationEntry) {
-                if (unseenFilterEnabled && !isShadeVisible) {
+                if (minimalismEnabled && !isShadeVisible) {
                     logger.logUnseenUpdated(entry.key)
                     unseenNotifications.add(entry)
                 }
             }
 
             override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
-                if (unseenFilterEnabled && unseenNotifications.remove(entry)) {
+                if (minimalismEnabled && unseenNotifications.remove(entry)) {
                     logger.logUnseenRemoved(entry.key)
                 }
             }
@@ -178,7 +178,7 @@
 
     private fun pickOutTopUnseenNotifs(list: List<ListEntry>) {
         if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return
-        if (!unseenFilterEnabled) return
+        if (!minimalismEnabled) return
         // Only ever elevate a top unseen notification on keyguard, not even locked shade
         if (statusBarStateController.state != StatusBarState.KEYGUARD) {
             seenNotificationsInteractor.setTopOngoingNotification(null)
@@ -215,6 +215,7 @@
             override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean =
                 when {
                     NotificationMinimalism.isUnexpectedlyInLegacyMode() -> false
+                    !minimalismEnabled -> false
                     seenNotificationsInteractor.isTopOngoingNotification(child) -> true
                     !NotificationMinimalism.ungroupTopUnseen -> false
                     else -> seenNotificationsInteractor.isTopUnseenNotification(child)
@@ -225,6 +226,7 @@
         object : NotifSectioner("TopOngoing", BUCKET_TOP_ONGOING) {
             override fun isInSection(entry: ListEntry): Boolean {
                 if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return false
+                if (!minimalismEnabled) return false
                 return entry.anyEntry { notificationEntry ->
                     seenNotificationsInteractor.isTopOngoingNotification(notificationEntry)
                 }
@@ -235,6 +237,7 @@
         object : NotifSectioner("TopUnseen", BUCKET_TOP_UNSEEN) {
             override fun isInSection(entry: ListEntry): Boolean {
                 if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return false
+                if (!minimalismEnabled) return false
                 return entry.anyEntry { notificationEntry ->
                     seenNotificationsInteractor.isTopUnseenNotification(notificationEntry)
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 3bc5495..5dff812 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -21,12 +21,14 @@
 import android.view.View.GONE
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState.KEYGUARD
 import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
@@ -38,6 +40,9 @@
 import kotlin.math.max
 import kotlin.math.min
 import kotlin.properties.Delegates.notNull
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
 
 private const val TAG = "NotifStackSizeCalc"
 private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG)
@@ -56,7 +61,9 @@
     private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
     private val mediaDataManager: MediaDataManager,
     @Main private val resources: Resources,
-    private val splitShadeStateController: SplitShadeStateController
+    private val splitShadeStateController: SplitShadeStateController,
+    private val seenNotificationsInteractor: SeenNotificationsInteractor,
+    @Application private val scope: CoroutineScope,
 ) {
 
     /**
@@ -74,7 +81,7 @@
 
     /** Whether we allow keyguard to show less important notifications above the shelf. */
     private val limitLockScreenToOneImportant
-        get() = NotificationMinimalism.isEnabled
+        get() = NotificationMinimalism.isEnabled && minimalismSettingEnabled
 
     /** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */
     private var dividerHeight by notNull<Float>()
@@ -85,8 +92,14 @@
      */
     private var saveSpaceOnLockscreen = false
 
+    /** True when the lock screen notification minimalism feature setting is enabled */
+    private var minimalismSettingEnabled = false
+
     init {
         updateResources()
+        if (NotificationMinimalism.isEnabled) {
+            scope.launch { trackLockScreenNotificationMinimalismSettingChanges() }
+        }
     }
 
     private fun allowedByPolicy(stackHeight: StackHeight): Boolean =
@@ -199,7 +212,7 @@
                     canStackFitInSpace(
                         heightResult,
                         notifSpace = notifSpace,
-                        shelfSpace = shelfSpace
+                        shelfSpace = shelfSpace,
                     ) == FitResult.FIT
             }
 
@@ -229,7 +242,7 @@
                         canStackFitInSpace(
                             heightResult,
                             notifSpace = notifSpace,
-                            shelfSpace = shelfSpace
+                            shelfSpace = shelfSpace,
                         ) != FitResult.NO_FIT
                 }
             log { "\t--- maxNotifications=$maxNotifications" }
@@ -277,7 +290,7 @@
     fun computeHeight(
         stack: NotificationStackScrollLayout,
         maxNotifs: Int,
-        shelfHeight: Float
+        shelfHeight: Float,
     ): Float {
         log { "\n" }
         log { "computeHeight ---" }
@@ -311,7 +324,7 @@
     private enum class FitResult {
         FIT,
         FIT_IF_SAVE_SPACE,
-        NO_FIT
+        NO_FIT,
     }
 
     data class SpaceNeeded(
@@ -319,7 +332,7 @@
         val whenEnoughSpace: Float,
 
         // Float height of space needed when showing collapsed layout for FSI HUNs.
-        val whenSavingSpace: Float
+        val whenSavingSpace: Float,
     )
 
     private data class StackHeight(
@@ -335,9 +348,19 @@
         val shelfHeightWithSpaceBefore: Float,
 
         /** Whether the stack should actually be forced into the shelf before this height. */
-        val shouldForceIntoShelf: Boolean
+        val shouldForceIntoShelf: Boolean,
     )
 
+    private suspend fun trackLockScreenNotificationMinimalismSettingChanges() {
+        if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return
+        seenNotificationsInteractor.isLockScreenNotificationMinimalismEnabled().collectLatest {
+            if (it != minimalismSettingEnabled) {
+                minimalismSettingEnabled = it
+            }
+            Log.i(TAG, "minimalismSettingEnabled: $minimalismSettingEnabled")
+        }
+    }
+
     private fun computeHeightPerNotificationLimit(
         stack: NotificationStackScrollLayout,
         shelfHeight: Float,
@@ -377,7 +400,7 @@
                             stack,
                             previous = currentNotification,
                             current = children[firstViewInShelfIndex],
-                            currentIndex = firstViewInShelfIndex
+                            currentIndex = firstViewInShelfIndex,
                         )
                     spaceBeforeShelf + shelfHeight
                 }
@@ -390,14 +413,15 @@
             log {
                 "\tcomputeHeightPerNotificationLimit i=$i notifs=$notifications " +
                     "notifsHeightSavingSpace=$notifsWithCollapsedHun" +
-                    " shelfWithSpaceBefore=$shelfWithSpaceBefore"
+                    " shelfWithSpaceBefore=$shelfWithSpaceBefore" +
+                    " limitLockScreenToOneImportant: $limitLockScreenToOneImportant"
             }
             yield(
                 StackHeight(
                     notifsHeight = notifications,
                     notifsHeightSavingSpace = notifsWithCollapsedHun,
                     shelfHeightWithSpaceBefore = shelfWithSpaceBefore,
-                    shouldForceIntoShelf = counter?.shouldForceIntoShelf() ?: false
+                    shouldForceIntoShelf = counter?.shouldForceIntoShelf() ?: false,
                 )
             )
         }
@@ -462,6 +486,10 @@
 
     fun dump(pw: PrintWriter, args: Array<out String>) {
         pw.println("NotificationStackSizeCalculator saveSpaceOnLockscreen=$saveSpaceOnLockscreen")
+        pw.println(
+            "NotificationStackSizeCalculator " +
+                "limitLockScreenToOneImportant=$limitLockScreenToOneImportant"
+        )
     }
 
     private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean {
@@ -484,7 +512,7 @@
         stack: NotificationStackScrollLayout,
         previous: ExpandableView?,
         current: ExpandableView?,
-        currentIndex: Int
+        currentIndex: Int,
     ): Float {
         if (currentIndex == 0) {
             return 0f
@@ -536,11 +564,7 @@
         takeWhile(predicate).count() - 1
 
     /** Counts the number of notifications for each type of bucket */
-    data class BucketTypeCounter(
-        var ongoing: Int = 0,
-        var important: Int = 0,
-        var other: Int = 0,
-    ) {
+    data class BucketTypeCounter(var ongoing: Int = 0, var important: Int = 0, var other: Int = 0) {
         fun incrementForBucket(@PriorityBucket bucket: Int?) {
             when (bucket) {
                 BUCKET_MEDIA_CONTROLS,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 76024cd..c7b6be3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -17,12 +17,15 @@
 package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
 
 import android.annotation.SuppressLint
+import android.content.Context
 import android.net.wifi.ScanResult
 import android.net.wifi.WifiManager
+import android.os.UserHandle
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.LifecycleRegistry
 import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.Flags.multiuserWifiPickerTrackerSupport
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -43,6 +46,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Unavailable.toHotspotDeviceType
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
+import com.android.systemui.user.data.repository.UserRepository
 import com.android.wifitrackerlib.HotspotNetworkEntry
 import com.android.wifitrackerlib.MergedCarrierEntry
 import com.android.wifitrackerlib.WifiEntry
@@ -53,10 +57,12 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -68,6 +74,8 @@
 class WifiRepositoryImpl
 @Inject
 constructor(
+    @Application applicationContext: Context,
+    private val userRepository: UserRepository,
     @Application private val scope: CoroutineScope,
     @Main private val mainExecutor: Executor,
     @Background private val bgDispatcher: CoroutineDispatcher,
@@ -84,90 +92,226 @@
 
     private var wifiPickerTracker: WifiPickerTracker? = null
 
-    private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run {
-        var current =
-            WifiPickerTrackerInfo(
-                state = WIFI_STATE_DEFAULT,
-                isDefault = false,
-                primaryNetwork = WIFI_NETWORK_DEFAULT,
-                secondaryNetworks = emptyList(),
-            )
-        callbackFlow {
-                val callback =
-                    object : WifiPickerTracker.WifiPickerTrackerCallback {
-                        override fun onWifiEntriesChanged() {
-                            val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
-                            logOnWifiEntriesChanged(connectedEntry)
+    @VisibleForTesting
+    val selectedUserContext: Flow<Context> =
+        userRepository.selectedUserInfo.map {
+            applicationContext.createContextAsUser(UserHandle.of(it.id), /* flags= */ 0)
+        }
 
-                            val activeNetworks = wifiPickerTracker?.activeWifiEntries ?: emptyList()
-                            val secondaryNetworks =
-                                activeNetworks
-                                    .filter { it != connectedEntry && !it.isPrimaryNetwork }
-                                    .map { it.toWifiNetworkModel() }
+    var current =
+        WifiPickerTrackerInfo(
+            state = WIFI_STATE_DEFAULT,
+            isDefault = false,
+            primaryNetwork = WIFI_NETWORK_DEFAULT,
+            secondaryNetworks = emptyList(),
+        )
 
-                            // [WifiPickerTracker.connectedWifiEntry] will return the same instance
-                            // but with updated internals. For example, when its validation status
-                            // changes from false to true, the same instance is re-used but with the
-                            // validated field updated.
-                            //
-                            // Because it's the same instance, the flow won't re-emit the value
-                            // (even though the internals have changed). So, we need to transform it
-                            // into our internal model immediately. [toWifiNetworkModel] always
-                            // returns a new instance, so the flow is guaranteed to emit.
-                            send(
-                                newPrimaryNetwork =
-                                    connectedEntry?.toPrimaryWifiNetworkModel()
-                                        ?: WIFI_NETWORK_DEFAULT,
-                                newSecondaryNetworks = secondaryNetworks,
-                                newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
-                            )
+    @kotlinx.coroutines.ExperimentalCoroutinesApi
+    private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> =
+        if (multiuserWifiPickerTrackerSupport()) {
+            selectedUserContext
+                .flatMapLatest { currentContext
+                    -> // flatMapLatest because when selectedUserContext emits a new value, we want
+                    // to
+                    // re-create a whole flow
+                    callbackFlow {
+                            val callback =
+                                object : WifiPickerTracker.WifiPickerTrackerCallback {
+                                    override fun onWifiEntriesChanged() {
+                                        val connectedEntry =
+                                            wifiPickerTracker.mergedOrPrimaryConnection
+                                        logOnWifiEntriesChanged(connectedEntry)
+
+                                        val activeNetworks =
+                                            wifiPickerTracker?.activeWifiEntries ?: emptyList()
+                                        val secondaryNetworks =
+                                            activeNetworks
+                                                .filter {
+                                                    it != connectedEntry && !it.isPrimaryNetwork
+                                                }
+                                                .map { it.toWifiNetworkModel() }
+
+                                        // [WifiPickerTracker.connectedWifiEntry] will return the
+                                        // same
+                                        // instance but with updated internals. For example, when
+                                        // its
+                                        // validation status changes from false to true, the same
+                                        // instance is re-used but with the validated field updated.
+                                        //
+                                        // Because it's the same instance, the flow won't re-emit
+                                        // the
+                                        // value (even though the internals have changed). So, we
+                                        // need
+                                        // to transform it into our internal model immediately.
+                                        // [toWifiNetworkModel] always returns a new instance, so
+                                        // the
+                                        // flow is guaranteed to emit.
+                                        send(
+                                            newPrimaryNetwork =
+                                                connectedEntry?.toPrimaryWifiNetworkModel()
+                                                    ?: WIFI_NETWORK_DEFAULT,
+                                            newSecondaryNetworks = secondaryNetworks,
+                                            newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
+                                        )
+                                    }
+
+                                    override fun onWifiStateChanged() {
+                                        val state = wifiPickerTracker?.wifiState
+                                        logOnWifiStateChanged(state)
+                                        send(newState = state ?: WIFI_STATE_DEFAULT)
+                                    }
+
+                                    override fun onNumSavedNetworksChanged() {}
+
+                                    override fun onNumSavedSubscriptionsChanged() {}
+
+                                    private fun send(
+                                        newState: Int = current.state,
+                                        newIsDefault: Boolean = current.isDefault,
+                                        newPrimaryNetwork: WifiNetworkModel =
+                                            current.primaryNetwork,
+                                        newSecondaryNetworks: List<WifiNetworkModel> =
+                                            current.secondaryNetworks,
+                                    ) {
+                                        val new =
+                                            WifiPickerTrackerInfo(
+                                                newState,
+                                                newIsDefault,
+                                                newPrimaryNetwork,
+                                                newSecondaryNetworks,
+                                            )
+                                        current = new
+                                        trySend(new)
+                                    }
+                                }
+                            wifiPickerTracker =
+                                wifiPickerTrackerFactory
+                                    .create(currentContext, lifecycle, callback, "WifiRepository")
+                                    .apply {
+                                        // By default, [WifiPickerTracker] will scan to see all
+                                        // available wifi networks in the area. Because SysUI only
+                                        // needs to display the **connected** network, we don't
+                                        // need scans to be running (and in fact, running scans is
+                                        // costly and should be avoided whenever possible).
+                                        this?.disableScanning()
+                                    }
+
+                            // The lifecycle must be STARTED in order for the callback to receive
+                            // events.
+                            mainExecutor.execute {
+                                lifecycle.currentState = Lifecycle.State.STARTED
+                            }
+                            awaitClose {
+                                mainExecutor.execute {
+                                    lifecycle.currentState = Lifecycle.State.CREATED
+                                }
+                            }
                         }
-
-                        override fun onWifiStateChanged() {
-                            val state = wifiPickerTracker?.wifiState
-                            logOnWifiStateChanged(state)
-                            send(newState = state ?: WIFI_STATE_DEFAULT)
-                        }
-
-                        override fun onNumSavedNetworksChanged() {}
-
-                        override fun onNumSavedSubscriptionsChanged() {}
-
-                        private fun send(
-                            newState: Int = current.state,
-                            newIsDefault: Boolean = current.isDefault,
-                            newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
-                            newSecondaryNetworks: List<WifiNetworkModel> =
-                                current.secondaryNetworks,
-                        ) {
-                            val new =
-                                WifiPickerTrackerInfo(
-                                    newState,
-                                    newIsDefault,
-                                    newPrimaryNetwork,
-                                    newSecondaryNetworks,
-                                )
-                            current = new
-                            trySend(new)
-                        }
-                    }
-
-                wifiPickerTracker =
-                    wifiPickerTrackerFactory.create(lifecycle, callback, "WifiRepository").apply {
-                        // By default, [WifiPickerTracker] will scan to see all available wifi
-                        // networks in the area. Because SysUI only needs to display the
-                        // **connected** network, we don't need scans to be running (and in fact,
-                        // running scans is costly and should be avoided whenever possible).
-                        this?.disableScanning()
-                    }
-                // The lifecycle must be STARTED in order for the callback to receive events.
-                mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
-                awaitClose {
-                    mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED }
+                        .stateIn(scope, SharingStarted.Eagerly, current)
                 }
+                .stateIn(scope, SharingStarted.Eagerly, current)
+        } else {
+
+            run {
+                var current =
+                    WifiPickerTrackerInfo(
+                        state = WIFI_STATE_DEFAULT,
+                        isDefault = false,
+                        primaryNetwork = WIFI_NETWORK_DEFAULT,
+                        secondaryNetworks = emptyList(),
+                    )
+                callbackFlow {
+                        val callback =
+                            object : WifiPickerTracker.WifiPickerTrackerCallback {
+                                override fun onWifiEntriesChanged() {
+                                    val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
+                                    logOnWifiEntriesChanged(connectedEntry)
+
+                                    val activeNetworks =
+                                        wifiPickerTracker?.activeWifiEntries ?: emptyList()
+                                    val secondaryNetworks =
+                                        activeNetworks
+                                            .filter { it != connectedEntry && !it.isPrimaryNetwork }
+                                            .map { it.toWifiNetworkModel() }
+
+                                    // [WifiPickerTracker.connectedWifiEntry] will return the same
+                                    // instance
+                                    // but with updated internals. For example, when its validation
+                                    // status
+                                    // changes from false to true, the same instance is re-used but
+                                    // with the
+                                    // validated field updated.
+                                    //
+                                    // Because it's the same instance, the flow won't re-emit the
+                                    // value
+                                    // (even though the internals have changed). So, we need to
+                                    // transform it
+                                    // into our internal model immediately. [toWifiNetworkModel]
+                                    // always
+                                    // returns a new instance, so the flow is guaranteed to emit.
+                                    send(
+                                        newPrimaryNetwork =
+                                            connectedEntry?.toPrimaryWifiNetworkModel()
+                                                ?: WIFI_NETWORK_DEFAULT,
+                                        newSecondaryNetworks = secondaryNetworks,
+                                        newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
+                                    )
+                                }
+
+                                override fun onWifiStateChanged() {
+                                    val state = wifiPickerTracker?.wifiState
+                                    logOnWifiStateChanged(state)
+                                    send(newState = state ?: WIFI_STATE_DEFAULT)
+                                }
+
+                                override fun onNumSavedNetworksChanged() {}
+
+                                override fun onNumSavedSubscriptionsChanged() {}
+
+                                private fun send(
+                                    newState: Int = current.state,
+                                    newIsDefault: Boolean = current.isDefault,
+                                    newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
+                                    newSecondaryNetworks: List<WifiNetworkModel> =
+                                        current.secondaryNetworks,
+                                ) {
+                                    val new =
+                                        WifiPickerTrackerInfo(
+                                            newState,
+                                            newIsDefault,
+                                            newPrimaryNetwork,
+                                            newSecondaryNetworks,
+                                        )
+                                    current = new
+                                    trySend(new)
+                                }
+                            }
+
+                        wifiPickerTracker =
+                            wifiPickerTrackerFactory
+                                .create(applicationContext, lifecycle, callback, "WifiRepository")
+                                .apply {
+                                    // By default, [WifiPickerTracker] will scan to see all
+                                    // available wifi
+                                    // networks in the area. Because SysUI only needs to display the
+                                    // **connected** network, we don't need scans to be running (and
+                                    // in fact,
+                                    // running scans is costly and should be avoided whenever
+                                    // possible).
+                                    this?.disableScanning()
+                                }
+                        // The lifecycle must be STARTED in order for the callback to receive
+                        // events.
+                        mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
+                        awaitClose {
+                            mainExecutor.execute {
+                                lifecycle.currentState = Lifecycle.State.CREATED
+                            }
+                        }
+                    }
+                    .stateIn(scope, SharingStarted.Eagerly, current)
             }
-            .stateIn(scope, SharingStarted.Eagerly, current)
-    }
+        }
 
     override val isWifiEnabled: StateFlow<Boolean> =
         wifiPickerTrackerInfo
@@ -185,11 +329,7 @@
         wifiPickerTrackerInfo
             .map { it.primaryNetwork }
             .distinctUntilChanged()
-            .logDiffsForTable(
-                tableLogger,
-                columnPrefix = "",
-                initialValue = WIFI_NETWORK_DEFAULT,
-            )
+            .logDiffsForTable(tableLogger, columnPrefix = "", initialValue = WIFI_NETWORK_DEFAULT)
             .stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT)
 
     override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
@@ -348,7 +488,7 @@
             TAG,
             LogLevel.DEBUG,
             { str1 = prettyPrintActivity(activity) },
-            { "onActivityChanged: $str1" }
+            { "onActivityChanged: $str1" },
         )
     }
 
@@ -379,13 +519,15 @@
         /** The currently primary wifi network. */
         val primaryNetwork: WifiNetworkModel,
         /** The current secondary network(s), if any. Specifically excludes the primary network. */
-        val secondaryNetworks: List<WifiNetworkModel>
+        val secondaryNetworks: List<WifiNetworkModel>,
     )
 
     @SysUISingleton
     class Factory
     @Inject
     constructor(
+        @Application private val applicationContext: Context,
+        private val userRepository: UserRepository,
         @Application private val scope: CoroutineScope,
         @Main private val mainExecutor: Executor,
         @Background private val bgDispatcher: CoroutineDispatcher,
@@ -395,6 +537,8 @@
     ) {
         fun create(wifiManager: WifiManager): WifiRepositoryImpl {
             return WifiRepositoryImpl(
+                applicationContext,
+                userRepository,
                 scope,
                 mainExecutor,
                 bgDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index c7bd5a1..9187e3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -206,12 +206,14 @@
     @SysUISingleton
     @Provides
     static AccessPointControllerImpl  provideAccessPointControllerImpl(
+            @Application Context context,
             UserManager userManager,
             UserTracker userTracker,
             @Main Executor mainExecutor,
             WifiPickerTrackerFactory wifiPickerTrackerFactory
     ) {
         AccessPointControllerImpl controller = new AccessPointControllerImpl(
+                context,
                 userManager,
                 userTracker,
                 mainExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index e9a33e0..ad97b21 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -348,42 +348,53 @@
 
     private suspend fun getSettings(): UserSwitcherSettingsModel {
         return withContext(backgroundDispatcher) {
-            val isSimpleUserSwitcher =
-                globalSettings.getInt(
-                    SETTING_SIMPLE_USER_SWITCHER,
-                    if (
-                        appContext.resources.getBoolean(
-                            com.android.internal.R.bool.config_expandLockScreenUserSwitcher
-                        )
-                    ) {
-                        1
-                    } else {
-                        0
-                    },
-                ) != 0
+            if (
+                // TODO(b/378068979): remove once login screen-specific logic
+                // is implemented at framework level.
+                appContext.resources.getBoolean(R.bool.config_userSwitchingMustGoThroughLoginScreen)
+            ) {
+                UserSwitcherSettingsModel(
+                    isSimpleUserSwitcher = false,
+                    isAddUsersFromLockscreen = false,
+                    isUserSwitcherEnabled = false,
+                )
+            } else {
+                val isSimpleUserSwitcher =
+                    globalSettings.getInt(
+                        SETTING_SIMPLE_USER_SWITCHER,
+                        if (
+                            appContext.resources.getBoolean(
+                                com.android.internal.R.bool.config_expandLockScreenUserSwitcher
+                            )
+                        ) {
+                            1
+                        } else {
+                            0
+                        },
+                    ) != 0
 
-            val isAddUsersFromLockscreen =
-                globalSettings.getInt(Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0
+                val isAddUsersFromLockscreen =
+                    globalSettings.getInt(Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0
 
-            val isUserSwitcherEnabled =
-                globalSettings.getInt(
-                    Settings.Global.USER_SWITCHER_ENABLED,
-                    if (
-                        appContext.resources.getBoolean(
-                            com.android.internal.R.bool.config_showUserSwitcherByDefault
-                        )
-                    ) {
-                        1
-                    } else {
-                        0
-                    },
-                ) != 0
-
-            UserSwitcherSettingsModel(
-                isSimpleUserSwitcher = isSimpleUserSwitcher,
-                isAddUsersFromLockscreen = isAddUsersFromLockscreen,
-                isUserSwitcherEnabled = isUserSwitcherEnabled,
-            )
+                val isUserSwitcherEnabled =
+                    globalSettings.getInt(
+                        Settings.Global.USER_SWITCHER_ENABLED,
+                        if (
+                            appContext.resources.getBoolean(
+                                com.android.internal.R.bool.config_showUserSwitcherByDefault
+                            )
+                        ) {
+                            1
+                        } else {
+                            0
+                        },
+                    ) != 0
+                UserSwitcherSettingsModel(
+                    isSimpleUserSwitcher = isSimpleUserSwitcher,
+                    isAddUsersFromLockscreen = isAddUsersFromLockscreen,
+                    isUserSwitcherEnabled = isUserSwitcherEnabled,
+                )
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
index a798360..bcbd679 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
@@ -21,6 +21,7 @@
 import android.os.Handler
 import android.os.UserManager
 import android.provider.Settings.Global.USER_SWITCHER_ENABLED
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -40,7 +41,6 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 interface UserSwitcherRepository {
@@ -67,6 +67,9 @@
     private val showUserSwitcherForSingleUser =
         context.resources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)
 
+    private val userSwitchingMustGoThroughLoginScreen =
+        context.resources.getBoolean(R.bool.config_userSwitchingMustGoThroughLoginScreen)
+
     override val isEnabled: Flow<Boolean> = conflatedCallbackFlow {
         suspend fun updateState() {
             trySendWithFailureLogging(isUserSwitcherEnabled(), TAG)
@@ -135,7 +138,13 @@
 
     private suspend fun isUserSwitcherEnabled(): Boolean {
         return withContext(bgDispatcher) {
-            userManager.isUserSwitcherEnabled(showUserSwitcherForSingleUser)
+            // TODO(b/378068979): remove once login screen-specific logic
+            // is implemented at framework level.
+            if (userSwitchingMustGoThroughLoginScreen) {
+                false
+            } else {
+                userManager.isUserSwitcherEnabled(showUserSwitcherForSingleUser)
+            }
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index c48898a..2e0b7c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -67,12 +67,12 @@
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
 import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.user.data.repository.userRepository
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.wifitrackerlib.MergedCarrierEntry
 import com.android.wifitrackerlib.WifiEntry
@@ -96,6 +96,8 @@
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -137,6 +139,7 @@
     private val wifiPickerTrackerCallback =
         argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
     private val vcnTransportInfo = VcnTransportInfo.Builder().build()
+    private val userRepository = kosmos.fakeUserRepository
 
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
@@ -159,7 +162,14 @@
             logcatTableLogBuffer(kosmos, "test")
         }
 
-        whenever(wifiPickerTrackerFactory.create(any(), capture(wifiPickerTrackerCallback), any()))
+        whenever(
+                wifiPickerTrackerFactory.create(
+                    any(),
+                    any(),
+                    capture(wifiPickerTrackerCallback),
+                    any(),
+                )
+            )
             .thenReturn(wifiPickerTracker)
 
         // For convenience, set up the subscription info callbacks
@@ -188,6 +198,8 @@
 
         wifiRepository =
             WifiRepositoryImpl(
+                mContext,
+                userRepository,
                 testScope.backgroundScope,
                 mainExecutor,
                 testDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 44e1437..d823bf5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -16,13 +16,19 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
 
+import android.content.Context
+import android.content.pm.UserInfo
 import android.net.wifi.ScanResult
 import android.net.wifi.WifiManager
 import android.net.wifi.WifiManager.UNKNOWN_SSID
 import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo
+import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.LogBuffer
@@ -33,12 +39,10 @@
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
 import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.user.data.repository.userRepository
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.fakeSystemClock
 import com.android.wifitrackerlib.HotspotNetworkEntry
 import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType
@@ -56,7 +60,12 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 /**
  * Note: Most of these tests are duplicates of [WifiRepositoryImplTest] tests.
@@ -67,8 +76,9 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-class WifiRepositoryImplTest : SysuiTestCase() {
+class WifiRepositoryImplTest() : SysuiTestCase() {
     private val kosmos = testKosmos()
+    private val userRepository = kosmos.fakeUserRepository
 
     // Using lazy means that the class will only be constructed once it's fetched. Because the
     // repository internally sets some values on construction, we need to set up some test
@@ -76,6 +86,8 @@
     // inside each test case without needing to manually recreate the repository.
     private val underTest: WifiRepositoryImpl by lazy {
         WifiRepositoryImpl(
+            mContext,
+            userRepository,
             testScope.backgroundScope,
             executor,
             dispatcher,
@@ -101,7 +113,8 @@
 
     @Before
     fun setUp() {
-        whenever(wifiPickerTrackerFactory.create(any(), capture(callbackCaptor), any()))
+        userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
+        whenever(wifiPickerTrackerFactory.create(any(), any(), capture(callbackCaptor), any()))
             .thenReturn(wifiPickerTracker)
     }
 
@@ -1203,6 +1216,95 @@
             assertThat(latest).isEmpty()
         }
 
+    // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
+    // this needs to
+    // be updated to capture the argument instead so currentUserContext can be private.
+    @Test
+    @EnableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
+    fun oneUserVerifyCreatingWifiPickerTracker_multiuserFlagEnabled() =
+        testScope.runTest {
+            val primaryUserMockContext = mock<Context>()
+            mContext.prepareCreateContextAsUser(
+                UserHandle.of(PRIMARY_USER_ID),
+                primaryUserMockContext,
+            )
+
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            runCurrent()
+            val currentUserContext by collectLastValue(underTest.selectedUserContext)
+
+            assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
+            verify(wifiPickerTrackerFactory).create(any(), any(), any(), any())
+        }
+
+    // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
+    // this needs to
+    // be updated to capture the argument instead so currentUserContext can be private.
+    @Test
+    @EnableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
+    fun changeUserVerifyCreatingWifiPickerTracker_multiuserEnabled() =
+        testScope.runTest {
+            val primaryUserMockContext = mock<Context>()
+            mContext.prepareCreateContextAsUser(
+                UserHandle.of(PRIMARY_USER_ID),
+                primaryUserMockContext,
+            )
+
+            runCurrent()
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            runCurrent()
+            val currentUserContext by collectLastValue(underTest.selectedUserContext)
+
+            assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
+
+            val otherUserMockContext = mock<Context>()
+            mContext.prepareCreateContextAsUser(
+                UserHandle.of(ANOTHER_USER_ID),
+                otherUserMockContext,
+            )
+
+            runCurrent()
+            userRepository.setSelectedUserInfo(ANOTHER_USER)
+            runCurrent()
+            val otherUserContext by collectLastValue(underTest.selectedUserContext)
+
+            assertThat(otherUserContext).isEqualTo(otherUserMockContext)
+            verify(wifiPickerTrackerFactory, times(2)).create(any(), any(), any(), any())
+        }
+
+    // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
+    // this needs to
+    // be updated to capture the argument instead so currentUserContext can be private.
+    @Test
+    @DisableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
+    fun changeUserVerifyCreatingWifiPickerTracker_multiuserDisabled() =
+        testScope.runTest {
+            val primaryUserMockContext = mock<Context>()
+            mContext.prepareCreateContextAsUser(
+                UserHandle.of(PRIMARY_USER_ID),
+                primaryUserMockContext,
+            )
+
+            runCurrent()
+            userRepository.setSelectedUserInfo(PRIMARY_USER)
+            runCurrent()
+            val currentUserContext by collectLastValue(underTest.selectedUserContext)
+
+            assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
+
+            val otherUserMockContext = mock<Context>()
+            mContext.prepareCreateContextAsUser(
+                UserHandle.of(ANOTHER_USER_ID),
+                otherUserMockContext,
+            )
+
+            runCurrent()
+            userRepository.setSelectedUserInfo(ANOTHER_USER)
+            runCurrent()
+
+            verify(wifiPickerTrackerFactory, times(1)).create(any(), any(), any(), any())
+        }
+
     private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback {
         testScope.runCurrent()
         return callbackCaptor.value
@@ -1231,5 +1333,20 @@
 
     private companion object {
         const val TITLE = "AB"
+        private const val PRIMARY_USER_ID = 0
+        private val PRIMARY_USER =
+            UserInfo(
+                /* id= */ PRIMARY_USER_ID,
+                /* name= */ "primary user",
+                /* flags= */ UserInfo.FLAG_PROFILE,
+            )
+
+        private const val ANOTHER_USER_ID = 1
+        private val ANOTHER_USER =
+            UserInfo(
+                /* id= */ ANOTHER_USER_ID,
+                /* name= */ "another user",
+                /* flags= */ UserInfo.FLAG_PROFILE,
+            )
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index 2dcd275..f6ff4c4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.deviceentry.data.repository
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
@@ -35,6 +36,9 @@
 
     private var pendingLockscreenEnabled = _isLockscreenEnabled.value
 
+    override val deviceUnlockStatus =
+        MutableStateFlow(DeviceUnlockStatus(isUnlocked = false, deviceUnlockSource = null))
+
     override suspend fun isLockscreenEnabled(): Boolean {
         _isLockscreenEnabled.value = pendingLockscreenEnabled
         return isLockscreenEnabled.value
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
index 8922b2f..be84e33 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
@@ -19,25 +19,23 @@
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
 import com.android.systemui.flags.fakeSystemPropertiesHelper
-import com.android.systemui.flags.systemPropertiesHelper
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.trustInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.power.domain.interactor.powerInteractor
 
 val Kosmos.deviceUnlockedInteractor by Fixture {
     DeviceUnlockedInteractor(
-        applicationScope = applicationCoroutineScope,
-        authenticationInteractor = authenticationInteractor,
-        deviceEntryRepository = deviceEntryRepository,
-        trustInteractor = trustInteractor,
-        faceAuthInteractor = deviceEntryFaceAuthInteractor,
-        fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
-        powerInteractor = powerInteractor,
-        biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
-        systemPropertiesHelper = fakeSystemPropertiesHelper,
-        keyguardTransitionInteractor = keyguardTransitionInteractor,
-    )
+            authenticationInteractor = authenticationInteractor,
+            repository = deviceEntryRepository,
+            trustInteractor = trustInteractor,
+            faceAuthInteractor = deviceEntryFaceAuthInteractor,
+            fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
+            powerInteractor = powerInteractor,
+            biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+            systemPropertiesHelper = fakeSystemPropertiesHelper,
+        )
+        .apply { activateIn(testScope) }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt
index 513d4e4..1395b18 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt
@@ -16,8 +16,10 @@
 
 package com.android.systemui.qs.panels.data.repository
 
+import com.android.systemui.broadcast.broadcastDispatcher
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.log.core.FakeLogBuffer
 import com.android.systemui.settings.userFileManager
 import com.android.systemui.user.data.repository.userRepository
 
@@ -27,6 +29,8 @@
             userFileManager,
             userRepository,
             defaultLargeTilesRepository,
-            testDispatcher
+            testDispatcher,
+            FakeLogBuffer.Factory.create(),
+            broadcastDispatcher,
         )
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 617cca9..d6fc6e4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -27,6 +27,8 @@
 import android.content.Context;
 import android.graphics.Region;
 import android.hardware.input.InputManager;
+import android.hardware.input.KeyGestureEvent;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.SystemClock;
@@ -44,11 +46,14 @@
 import android.view.MotionEvent.PointerProperties;
 import android.view.accessibility.AccessibilityEvent;
 
+import androidx.annotation.Nullable;
+
 import com.android.server.LocalServices;
 import com.android.server.accessibility.gestures.TouchExplorer;
 import com.android.server.accessibility.magnification.FullScreenMagnificationController;
 import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
 import com.android.server.accessibility.magnification.FullScreenMagnificationVibrationHelper;
+import com.android.server.accessibility.magnification.MagnificationController;
 import com.android.server.accessibility.magnification.MagnificationGestureHandler;
 import com.android.server.accessibility.magnification.MouseEventHandler;
 import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler;
@@ -187,6 +192,8 @@
 
     private final AccessibilityManagerService mAms;
 
+    private final InputManager mInputManager;
+
     private final SparseArray<EventStreamTransformation> mEventHandler;
 
     private final SparseArray<TouchExplorer> mTouchExplorer = new SparseArray<>(0);
@@ -228,6 +235,47 @@
      */
     private MotionEvent mLastActiveDeviceMotionEvent = null;
 
+    private boolean mKeyGestureEventHandlerInstalled = false;
+    private InputManager.KeyGestureEventHandler mKeyGestureEventHandler =
+            new InputManager.KeyGestureEventHandler() {
+                @Override
+                public boolean handleKeyGestureEvent(
+                        @NonNull KeyGestureEvent event,
+                        @Nullable IBinder focusedToken) {
+                    final boolean complete =
+                            event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE
+                                    && !event.isCancelled();
+                    final int gestureType = event.getKeyGestureType();
+                    final int displayId = isDisplayIdValid(event.getDisplayId())
+                            ? event.getDisplayId() : Display.DEFAULT_DISPLAY;
+
+                    switch (gestureType) {
+                        case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN:
+                            if (complete) {
+                                mAms.getMagnificationController().scaleMagnificationByStep(
+                                        displayId, MagnificationController.ZOOM_DIRECTION_IN);
+                            }
+                            return true;
+                        case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT:
+                            if (complete) {
+                                mAms.getMagnificationController().scaleMagnificationByStep(
+                                        displayId, MagnificationController.ZOOM_DIRECTION_OUT);
+                            }
+                            return true;
+                    }
+                    return false;
+                }
+
+                @Override
+                public boolean isKeyGestureSupported(int gestureType) {
+                    return switch (gestureType) {
+                        case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN,
+                             KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT -> true;
+                        default -> false;
+                    };
+                }
+            };
+
     private static MotionEvent cancelMotion(MotionEvent event) {
         if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
                 || event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT
@@ -287,6 +335,7 @@
         mContext = context;
         mAms = service;
         mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mInputManager = context.getSystemService(InputManager.class);
         mEventHandler = eventHandler;
     }
 
@@ -723,6 +772,12 @@
                     createMagnificationGestureHandler(displayId, displayContext);
             addFirstEventHandler(displayId, magnificationGestureHandler);
             mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
+
+            if (com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures()
+                    && !mKeyGestureEventHandlerInstalled) {
+                mInputManager.registerKeyGestureEventHandler(mKeyGestureEventHandler);
+                mKeyGestureEventHandlerInstalled = true;
+            }
         }
 
         if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
@@ -842,6 +897,11 @@
             mMouseKeysInterceptor.onDestroy();
             mMouseKeysInterceptor = null;
         }
+
+        if (mKeyGestureEventHandlerInstalled) {
+            mInputManager.unregisterKeyGestureEventHandler(mKeyGestureEventHandler);
+            mKeyGestureEventHandlerInstalled = false;
+        }
     }
 
     private MagnificationGestureHandler createMagnificationGestureHandler(
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index d40e747..51c4305 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -27,6 +27,7 @@
 import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID;
 
 import android.accessibilityservice.MagnificationConfig;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -101,6 +102,7 @@
     private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
     /** Whether the platform supports window magnification feature. */
     private final boolean mSupportWindowMagnification;
+    private final MagnificationScaleStepProvider mScaleStepProvider;
 
     private final Executor mBackgroundExecutor;
 
@@ -131,6 +133,14 @@
             .UiChangesForAccessibilityCallbacks> mAccessibilityCallbacksDelegateArray =
             new SparseArray<>();
 
+    // Direction magnifier scale can be altered.
+    public static final int ZOOM_DIRECTION_IN = 0;
+    public static final int ZOOM_DIRECTION_OUT = 1;
+
+    @IntDef({ZOOM_DIRECTION_IN, ZOOM_DIRECTION_OUT})
+    public @interface ZoomDirection {
+    }
+
     /**
      * A callback to inform the magnification transition result on the given display.
      */
@@ -144,6 +154,41 @@
         void onResult(int displayId, boolean success);
     }
 
+
+    /**
+     * An interface to configure how much the magnification scale should be affected when moving in
+     * steps.
+     */
+    public interface MagnificationScaleStepProvider {
+        /**
+         * Calculate the next value given which direction (in/out) to adjust the magnification
+         * scale.
+         *
+         * @param currentScale The current magnification scale value.
+         * @param direction    Whether to zoom in or out.
+         * @return The next scale value.
+         */
+        float nextScaleStep(float currentScale, @ZoomDirection int direction);
+    }
+
+    public static class DefaultMagnificationScaleStepProvider implements
+            MagnificationScaleStepProvider {
+        // Factor of magnification scale. For example, when this value is 1.189, scale
+        // value will be changed x1.000, x1.189, x1.414, x1.681, x2.000, ...
+        // Note: this value is 2.0 ^ (1 / 4).
+        public static final float ZOOM_STEP_SCALE_FACTOR = 1.18920712f;
+
+        @Override
+        public float nextScaleStep(float currentScale, @ZoomDirection int direction) {
+            final int stepDelta = direction == ZOOM_DIRECTION_IN ? 1 : -1;
+            final long scaleIndex = Math.round(
+                    Math.log(currentScale) / Math.log(ZOOM_STEP_SCALE_FACTOR));
+            final float nextScale = (float) Math.pow(ZOOM_STEP_SCALE_FACTOR,
+                    scaleIndex + stepDelta);
+            return MagnificationScaleProvider.constrainScale(nextScale);
+        }
+    }
+
     public MagnificationController(AccessibilityManagerService ams, Object lock,
             Context context, MagnificationScaleProvider scaleProvider,
             Executor backgroundExecutor) {
@@ -156,6 +201,7 @@
                 .getAccessibilityController().setUiChangesForAccessibilityCallbacks(this);
         mSupportWindowMagnification = context.getPackageManager().hasSystemFeature(
                 FEATURE_WINDOW_MAGNIFICATION);
+        mScaleStepProvider = new DefaultMagnificationScaleStepProvider();
 
         mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(context);
         mAlwaysOnMagnificationFeatureFlag.addOnChangedListener(
@@ -891,6 +937,37 @@
         return isActivated;
     }
 
+    /**
+     * Scales the magnifier on the given display one step in/out based on the zoomIn param.
+     *
+     * @param displayId The logical display id.
+     * @param direction Whether the scale should be zoomed in or out.
+     * @return {@code true} if the magnification scale was affected.
+     */
+    public boolean scaleMagnificationByStep(int displayId, @ZoomDirection int direction) {
+        if (getFullScreenMagnificationController().isActivated(displayId)) {
+            final float magnificationScale = getFullScreenMagnificationController().getScale(
+                    displayId);
+            final float nextMagnificationScale = mScaleStepProvider.nextScaleStep(
+                    magnificationScale, direction);
+            getFullScreenMagnificationController().setScaleAndCenter(displayId,
+                    nextMagnificationScale,
+                    Float.NaN, Float.NaN, true, MAGNIFICATION_GESTURE_HANDLER_ID);
+            return nextMagnificationScale != magnificationScale;
+        }
+
+        if (getMagnificationConnectionManager().isWindowMagnifierEnabled(displayId)) {
+            final float magnificationScale = getMagnificationConnectionManager().getScale(
+                    displayId);
+            final float nextMagnificationScale = mScaleStepProvider.nextScaleStep(
+                    magnificationScale, direction);
+            getMagnificationConnectionManager().setScale(displayId, nextMagnificationScale);
+            return nextMagnificationScale != magnificationScale;
+        }
+
+        return false;
+    }
+
     private final class DisableMagnificationCallback implements
             MagnificationAnimationCallback {
         private final TransitionCallBack mTransitionCallBack;
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index ddccb37..466d477 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -281,6 +281,9 @@
     private static final int SCHEDULE_FILE_VERSION = 1;
 
     public static final String SETTINGS_PACKAGE = "com.android.providers.settings";
+
+    public static final String TELEPHONY_PROVIDER_PACKAGE = "com.android.providers.telephony";
+
     public static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
 
     // Pseudoname that we use for the Package Manager metadata "package".
diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
index f24a3c1..508b62c 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
@@ -21,6 +21,7 @@
 import static com.android.server.backup.UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
 import static com.android.server.backup.UserBackupManagerService.SETTINGS_PACKAGE;
 import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
+import static com.android.server.backup.UserBackupManagerService.TELEPHONY_PROVIDER_PACKAGE;
 import static com.android.server.backup.UserBackupManagerService.WALLPAPER_PACKAGE;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
@@ -75,6 +76,12 @@
             systemPackagesAllowedForProfileUser,
             Sets.newArraySet(WALLPAPER_PACKAGE, SETTINGS_PACKAGE));
 
+    static {
+        if (UserManager.isHeadlessSystemUserMode()) {
+            systemPackagesAllowedForNonSystemUsers.add(TELEPHONY_PROVIDER_PACKAGE);
+        }
+    }
+
     private final PackageManager mPackageManager;
     private final PackageManagerInternal mPackageManagerInternal;
     private final int mUserId;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 7f482ac..aea16b0 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -238,6 +238,7 @@
         "connectivity_flags_lib",
         "device_config_service_flags_java",
         "dreams_flags_lib",
+        "aconfig_flags_java",
         "aconfig_new_storage_flags_lib",
         "powerstats_flags_lib",
         "locksettings_flags_lib",
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 72a9a2d..fa22862 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -88,6 +88,7 @@
 import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.MediaQualityStatus;
+import android.telephony.satellite.NtnSignalStrength;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -440,6 +441,7 @@
     private boolean[] mCarrierRoamingNtnEligible = null;
 
     private List<IntArray> mCarrierRoamingNtnAvailableServices;
+    private NtnSignalStrength[] mCarrierRoamingNtnSignalStrength;
 
     // Local cache to check if Satellite Modem is enabled
     private AtomicBoolean mIsSatelliteEnabled;
@@ -745,6 +747,12 @@
             mSCBMDuration = copyOf(mSCBMDuration, mNumPhones);
             mCarrierRoamingNtnMode = copyOf(mCarrierRoamingNtnMode, mNumPhones);
             mCarrierRoamingNtnEligible = copyOf(mCarrierRoamingNtnEligible, mNumPhones);
+            if (mCarrierRoamingNtnSignalStrength != null) {
+                mCarrierRoamingNtnSignalStrength = copyOf(
+                        mCarrierRoamingNtnSignalStrength, mNumPhones);
+            } else {
+                mCarrierRoamingNtnSignalStrength = new NtnSignalStrength[mNumPhones];
+            }
             // ds -> ss switch.
             if (mNumPhones < oldNumPhones) {
                 cutListToSize(mCellInfo, mNumPhones);
@@ -807,6 +815,8 @@
                 mCarrierRoamingNtnMode[i] = false;
                 mCarrierRoamingNtnEligible[i] = false;
                 mCarrierRoamingNtnAvailableServices.add(i, new IntArray());
+                mCarrierRoamingNtnSignalStrength[i] = new NtnSignalStrength(
+                        NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE);
             }
         }
     }
@@ -883,6 +893,7 @@
         mCarrierRoamingNtnMode = new boolean[numPhones];
         mCarrierRoamingNtnEligible = new boolean[numPhones];
         mCarrierRoamingNtnAvailableServices = new ArrayList<>();
+        mCarrierRoamingNtnSignalStrength = new NtnSignalStrength[numPhones];
         mIsSatelliteEnabled = new AtomicBoolean();
         mWasSatelliteEnabledNotified = new AtomicBoolean();
 
@@ -932,6 +943,8 @@
             mCarrierRoamingNtnMode[i] = false;
             mCarrierRoamingNtnEligible[i] = false;
             mCarrierRoamingNtnAvailableServices.add(i, new IntArray());
+            mCarrierRoamingNtnSignalStrength[i] = new NtnSignalStrength(
+                    NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE);
         }
 
         mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -1565,6 +1578,15 @@
                         remove(r.binder);
                     }
                 }
+                if (events.contains(
+                        TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED)) {
+                    try {
+                        r.callback.onCarrierRoamingNtnSignalStrengthChanged(
+                                mCarrierRoamingNtnSignalStrength[r.phoneId]);
+                    } catch (RemoteException ex) {
+                        remove(r.binder);
+                    }
+                }
             }
         }
     }
@@ -3803,6 +3825,44 @@
         }
     }
 
+
+    /**
+     * Notify external listeners that carrier roaming non-terrestrial network
+     * signal strength changed.
+     * @param subId subscription ID.
+     * @param ntnSignalStrength non-terrestrial network signal strength.
+     */
+    public void notifyCarrierRoamingNtnSignalStrengthChanged(int subId,
+            @NonNull NtnSignalStrength ntnSignalStrength) {
+        if (!checkNotifyPermission("notifyCarrierRoamingNtnSignalStrengthChanged")) {
+            log("nnotifyCarrierRoamingNtnSignalStrengthChanged: caller does not have required "
+                    + "permissions.");
+            return;
+        }
+
+        if (VDBG) {
+            log("notifyCarrierRoamingNtnSignalStrengthChanged: "
+                    + "subId=" + subId + " ntnSignalStrength=" + ntnSignalStrength.getLevel());
+        }
+
+        synchronized (mRecords) {
+            int phoneId = getPhoneIdFromSubId(subId);
+            mCarrierRoamingNtnSignalStrength[phoneId] = ntnSignalStrength;
+            for (Record r : mRecords) {
+                if (r.matchTelephonyCallbackEvent(
+                        TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED)
+                        && idMatch(r, subId, phoneId)) {
+                    try {
+                        r.callback.onCarrierRoamingNtnSignalStrengthChanged(ntnSignalStrength);
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
     @NeverCompile // Avoid size overhead of debugging code.
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
@@ -3858,6 +3918,8 @@
                 pw.println("mSCBMDuration=" + mSCBMDuration[i]);
                 pw.println("mCarrierRoamingNtnMode=" + mCarrierRoamingNtnMode[i]);
                 pw.println("mCarrierRoamingNtnEligible=" + mCarrierRoamingNtnEligible[i]);
+                pw.println("mCarrierRoamingNtnSignalStrength="
+                        + mCarrierRoamingNtnSignalStrength[i]);
 
                 // We need to obfuscate package names, and primitive arrays' native toString is ugly
                 Pair<List<String>, int[]> carrierPrivilegeState = mCarrierPrivilegeStates.get(i);
diff --git a/services/core/java/com/android/server/adaptiveauth/OWNERS b/services/core/java/com/android/server/adaptiveauth/OWNERS
deleted file mode 100644
index b188105..0000000
--- a/services/core/java/com/android/server/adaptiveauth/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hainingc@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 18e8abb..0826c53 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19307,18 +19307,31 @@
     public void addCreatorToken(@Nullable Intent intent, String creatorPackage) {
         if (!preventIntentRedirect()) return;
 
-        if (intent == null) return;
-        intent.forEachNestedCreatorToken(extraIntent -> {
-            IntentCreatorToken creatorToken = createIntentCreatorToken(extraIntent, creatorPackage);
-            if (creatorToken != null) {
-                extraIntent.setCreatorToken(creatorToken);
-                // TODO remove Slog.wtf once proven FrameworkStatsLog works. b/375396329
-                Slog.wtf(TAG, "A creator token is added to an intent. creatorPackage: "
-                        + creatorPackage + "; intent: " + intent);
-                FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED,
-                        creatorToken.getCreatorUid());
+        if (intent == null || intent.getExtraIntentKeys() == null) return;
+        for (String key : intent.getExtraIntentKeys()) {
+            try {
+                Intent extraIntent = intent.getParcelableExtra(key, Intent.class);
+                if (extraIntent == null) {
+                    Slog.w(TAG, "The key {" + key
+                            + "} does not correspond to an intent in the extra bundle.");
+                    continue;
+                }
+                IntentCreatorToken creatorToken = createIntentCreatorToken(extraIntent,
+                        creatorPackage);
+                if (creatorToken != null) {
+                    extraIntent.setCreatorToken(creatorToken);
+                    Slog.wtf(TAG, "A creator token is added to an intent. creatorPackage: "
+                            + creatorPackage + "; intent: " + intent);
+                    FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED,
+                            creatorToken.getCreatorUid());
+                }
+            } catch (Exception e) {
+                Slog.wtf(TAG,
+                        "Something went wrong when trying to add creator token for embedded "
+                                + "intents of intent: ."
+                                + intent, e);
             }
-        });
+        }
     }
 
     private IntentCreatorToken createIntentCreatorToken(Intent intent, String creatorPackage) {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 8dc7c73..3dd5ec9 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -42,6 +42,7 @@
 import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
 import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
 import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately;
+import static com.android.aconfig.flags.Flags.enableSystemAconfigdRust;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -144,7 +145,6 @@
         "android_core_networking",
         "android_health_services",
         "android_sdk",
-        "android_stylus",
         "aoc",
         "app_widgets",
         "arc_next",
@@ -209,6 +209,7 @@
         "pixel_continuity",
         "pixel_perf",
         "pixel_sensors",
+        "pixel_state_server",
         "pixel_system_sw_video",
         "pixel_video_sw",
         "pixel_watch",
@@ -456,9 +457,11 @@
     static ProtoInputStream sendAconfigdRequests(ProtoOutputStream requests) {
         // connect to aconfigd socket
         LocalSocket client = new LocalSocket();
+        String socketName = enableSystemAconfigdRust()
+                    ? "aconfigd_system" : "aconfigd";
         try{
             client.connect(new LocalSocketAddress(
-                "aconfigd", LocalSocketAddress.Namespace.RESERVED));
+                socketName, LocalSocketAddress.Namespace.RESERVED));
             Slog.d(TAG, "connected to aconfigd socket");
         } catch (IOException ioe) {
             logErr("failed to connect to aconfigd socket", ioe);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0cf55bb..6ba3569 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -9193,16 +9193,6 @@
             mIndexMin = MIN_STREAM_VOLUME[streamType] * 10;
             mIndexMax = MAX_STREAM_VOLUME[streamType] * 10;
 
-            final int status = AudioSystem.initStreamVolume(
-                    streamType, MIN_STREAM_VOLUME[streamType], MAX_STREAM_VOLUME[streamType]);
-            if (status != AudioSystem.AUDIO_STATUS_OK) {
-                sLifecycleLogger.enqueue(new EventLogger.StringEvent(
-                         "VSS() stream:" + streamType + " initStreamVolume=" + status)
-                        .printLog(ALOGE, TAG));
-                sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
-                        "VSS()" /*obj*/, 2 * INDICATE_SYSTEM_READY_RETRY_DELAY_MS);
-            }
-
             updateIndexFactors();
             mIndexMinNoPerm = mIndexMin; // may be overwritten later in updateNoPermMinIndex()
 
@@ -9267,6 +9257,19 @@
                     mIndexMinNoPerm = mIndexMin;
                 }
             }
+
+            final int status = AudioSystem.initStreamVolume(
+                    mStreamType, mIndexMin / 10, mIndexMax / 10);
+            sVolumeLogger.enqueue(new EventLogger.StringEvent(
+                    "updateIndexFactors() stream:" + mStreamType + " index min/max:"
+                            + mIndexMin / 10 + "/" + mIndexMax / 10 + " indexStepFactor:"
+                            + mIndexStepFactor).printSlog(ALOGI, TAG));
+            if (status != AudioSystem.AUDIO_STATUS_OK) {
+                sVolumeLogger.enqueue(new EventLogger.StringEvent(
+                        "Failed initStreamVolume with status=" + status).printSlog(ALOGE, TAG));
+                sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
+                        "updateIndexFactors()" /*obj*/, 2 * INDICATE_SYSTEM_READY_RETRY_DELAY_MS);
+            }
         }
 
         /**
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 00280c8f..6578023 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -288,11 +288,13 @@
          * @param handler The handler to run {@link #onChange} on, or null if none.
          */
         public SettingObserver(Context context, Handler handler,
-                List<BiometricService.EnabledOnKeyguardCallback> callbacks) {
+                List<BiometricService.EnabledOnKeyguardCallback> callbacks,
+                UserManager userManager, FingerprintManager fingerprintManager,
+                FaceManager faceManager) {
             super(handler);
             mContentResolver = context.getContentResolver();
             mCallbacks = callbacks;
-            mUserManager = context.getSystemService(UserManager.class);
+            mUserManager = userManager;
 
             final boolean hasFingerprint = context.getPackageManager()
                     .hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
@@ -304,7 +306,7 @@
                     Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.Q
                     && hasFace && !hasFingerprint;
 
-            addBiometricListenersForMandatoryBiometrics(context);
+            addBiometricListenersForMandatoryBiometrics(context, fingerprintManager, faceManager);
             updateContentObserver();
         }
 
@@ -431,11 +433,21 @@
 
         public boolean getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(int userId) {
             if (!mMandatoryBiometricsEnabled.containsKey(userId)) {
+                Slog.d(TAG, "update mb toggle for user " + userId);
                 updateMandatoryBiometricsForAllProfiles(userId);
             }
             if (!mMandatoryBiometricsRequirementsSatisfied.containsKey(userId)) {
+                Slog.d(TAG, "update mb reqs for user " + userId);
                 updateMandatoryBiometricsRequirementsForAllProfiles(userId);
             }
+
+            Slog.d(TAG, mMandatoryBiometricsEnabled.getOrDefault(userId,
+                    DEFAULT_MANDATORY_BIOMETRICS_STATUS)
+                    + " " + mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
+                    DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS)
+                    + " " + getEnabledForApps(userId)
+                    + " " + (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */)
+                    || mFaceEnrolledForUser.getOrDefault(userId, false /* default */)));
             return mMandatoryBiometricsEnabled.getOrDefault(userId,
                     DEFAULT_MANDATORY_BIOMETRICS_STATUS)
                     && mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
@@ -456,11 +468,23 @@
 
         private void updateMandatoryBiometricsForAllProfiles(int userId) {
             int effectiveUserId = userId;
-            if (mUserManager.getMainUser() != null) {
-                effectiveUserId = mUserManager.getMainUser().getIdentifier();
+            final UserInfo parentProfile = mUserManager.getProfileParent(userId);
+
+            if (parentProfile != null) {
+                effectiveUserId = parentProfile.id;
             }
-            for (int profileUserId: mUserManager.getEnabledProfileIds(effectiveUserId)) {
-                mMandatoryBiometricsEnabled.put(profileUserId,
+
+            final int[] enabledProfileIds = mUserManager.getEnabledProfileIds(effectiveUserId);
+            if (enabledProfileIds != null) {
+                for (int profileUserId : enabledProfileIds) {
+                    mMandatoryBiometricsEnabled.put(profileUserId,
+                            Settings.Secure.getIntForUser(
+                                    mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS,
+                                    DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0,
+                                    effectiveUserId) != 0);
+                }
+            } else {
+                mMandatoryBiometricsEnabled.put(userId,
                         Settings.Secure.getIntForUser(
                                 mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS,
                                 DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0,
@@ -470,11 +494,24 @@
 
         private void updateMandatoryBiometricsRequirementsForAllProfiles(int userId) {
             int effectiveUserId = userId;
-            if (mUserManager.getMainUser() != null) {
-                effectiveUserId = mUserManager.getMainUser().getIdentifier();
+            final UserInfo parentProfile = mUserManager.getProfileParent(userId);
+
+            if (parentProfile != null) {
+                effectiveUserId = parentProfile.id;
             }
-            for (int profileUserId: mUserManager.getEnabledProfileIds(effectiveUserId)) {
-                mMandatoryBiometricsRequirementsSatisfied.put(profileUserId,
+
+            final int[] enabledProfileIds = mUserManager.getEnabledProfileIds(effectiveUserId);
+            if (enabledProfileIds != null) {
+                for (int profileUserId : enabledProfileIds) {
+                    mMandatoryBiometricsRequirementsSatisfied.put(profileUserId,
+                            Settings.Secure.getIntForUser(mContentResolver,
+                                    Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
+                                    DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS
+                                            ? 1 : 0,
+                                    effectiveUserId) != 0);
+                }
+            } else {
+                mMandatoryBiometricsRequirementsSatisfied.put(userId,
                         Settings.Secure.getIntForUser(mContentResolver,
                                 Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
                                 DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS ? 1 : 0,
@@ -482,10 +519,8 @@
             }
         }
 
-        private void addBiometricListenersForMandatoryBiometrics(Context context) {
-            final FingerprintManager fingerprintManager = context.getSystemService(
-                    FingerprintManager.class);
-            final FaceManager faceManager = context.getSystemService(FaceManager.class);
+        private void addBiometricListenersForMandatoryBiometrics(Context context,
+                FingerprintManager fingerprintManager, FaceManager faceManager) {
             if (fingerprintManager != null) {
                 fingerprintManager.addAuthenticatorsRegisteredCallback(
                         new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -1169,7 +1204,9 @@
          */
         public SettingObserver getSettingObserver(Context context, Handler handler,
                 List<EnabledOnKeyguardCallback> callbacks) {
-            return new SettingObserver(context, handler, callbacks);
+            return new SettingObserver(context, handler, callbacks, context.getSystemService(
+                    UserManager.class), context.getSystemService(FingerprintManager.class),
+                    context.getSystemService(FaceManager.class));
         }
 
         /**
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index e545dd5..2e7f5c0 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -215,6 +215,12 @@
             systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_T,
                     KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
                     KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK));
+            systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_MINUS,
+                    KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+                    KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT));
+            systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_EQUALS,
+                    KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+                    KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN));
         }
         if (keyboardA11yShortcutControl()) {
             if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index a45ea1d..21ae182 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -59,15 +59,15 @@
             return pp;
         }
         @Override
-        public void updatePictureProfile(long id, PictureProfile pp) {
+        public void updatePictureProfile(String id, PictureProfile pp) {
             // TODO: implement
         }
         @Override
-        public void removePictureProfile(long id) {
+        public void removePictureProfile(String id) {
             // TODO: implement
         }
         @Override
-        public PictureProfile getPictureProfileById(long id) {
+        public PictureProfile getPictureProfile(int type, String name) {
             return null;
         }
         @Override
@@ -79,7 +79,7 @@
             return new ArrayList<>();
         }
         @Override
-        public List<PictureProfile> getAllPictureProfiles() {
+        public List<String> getPictureProfilePackageNames() {
             return new ArrayList<>();
         }
 
@@ -89,15 +89,15 @@
             return pp;
         }
         @Override
-        public void updateSoundProfile(long id, SoundProfile pp) {
+        public void updateSoundProfile(String id, SoundProfile pp) {
             // TODO: implement
         }
         @Override
-        public void removeSoundProfile(long id) {
+        public void removeSoundProfile(String id) {
             // TODO: implement
         }
         @Override
-        public SoundProfile getSoundProfileById(long id) {
+        public SoundProfile getSoundProfileById(String id) {
             return null;
         }
         @Override
@@ -109,7 +109,7 @@
             return new ArrayList<>();
         }
         @Override
-        public List<SoundProfile> getAllSoundProfiles() {
+        public List<String> getSoundProfilePackageNames() {
             return new ArrayList<>();
         }
 
@@ -138,6 +138,14 @@
             return new ArrayList<>();
         }
 
+        @Override
+        public List<String> getPictureProfileAllowList() {
+            return new ArrayList<>();
+        }
+
+        @Override
+        public void setPictureProfileAllowList(List<String> packages) {
+        }
 
         @Override
         public boolean isSupported() {
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 5febd5c..5914dbe 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -788,6 +788,20 @@
                 return;
             }
 
+            // Check if summary & child notifications are not part of the same section/bundle
+            // Needs a check here if notification was bundled while enqueued
+            if (notificationRegroupOnClassification()
+                    && android.service.notification.Flags.notificationClassification()) {
+                if (isGroupChildBundled(record, summaryByGroupKey)) {
+                    if (DEBUG) {
+                        Slog.v(TAG, "isGroupChildInDifferentBundleThanSummary: " + record);
+                    }
+                    moveNotificationsToNewSection(record.getUserId(), pkgName,
+                            List.of(new NotificationMoveOp(record, null, fullAggregateGroupKey)));
+                    return;
+                }
+            }
+
             // scenario 3: sparse/singleton groups
             if (Flags.notificationForceGroupSingletons()) {
                 try {
@@ -800,6 +814,27 @@
         }
     }
 
+    private static boolean isGroupChildBundled(final NotificationRecord record,
+            final Map<String, NotificationRecord> summaryByGroupKey) {
+        final StatusBarNotification sbn = record.getSbn();
+        final String groupKey = record.getSbn().getGroupKey();
+
+        if (!sbn.isAppGroup()) {
+            return false;
+        }
+
+        if (record.getNotification().isGroupSummary()) {
+            return false;
+        }
+
+        final NotificationRecord summary = summaryByGroupKey.get(groupKey);
+        if (summary == null) {
+            return false;
+        }
+
+        return NotificationChannel.SYSTEM_RESERVED_IDS.contains(record.getChannel().getId());
+    }
+
     /**
      * Called when a notification is removed, so that this helper can adjust the aggregate groups:
      *  - Removes the autogroup summary of the notification's section
@@ -1598,7 +1633,7 @@
         final int mSummaryId;
         private final Predicate<NotificationRecord> mSectionChecker;
 
-        public NotificationSectioner(String name, int summaryId,
+        private NotificationSectioner(String name, int summaryId,
                 Predicate<NotificationRecord> sectionChecker) {
             mName = name;
             mSummaryId = summaryId;
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 93482e7..b0ef807 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -75,9 +75,7 @@
 import com.android.internal.util.function.TriPredicate;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.LocalServices;
 import com.android.server.notification.NotificationManagerService.DumpFilter;
-import com.android.server.pm.UserManagerInternal;
 import com.android.server.utils.TimingsTraceAndSlog;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -136,7 +134,6 @@
     private final UserProfiles mUserProfiles;
     protected final IPackageManager mPm;
     protected final UserManager mUm;
-    private final UserManagerInternal mUserManagerInternal;
     private final Config mConfig;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
@@ -198,7 +195,6 @@
         mConfig = getConfig();
         mApprovalLevel = APPROVAL_BY_COMPONENT;
         mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
     }
 
     abstract protected Config getConfig();
@@ -1389,14 +1385,9 @@
     @GuardedBy("mMutex")
     protected void populateComponentsToBind(SparseArray<Set<ComponentName>> componentsToBind,
             final IntArray activeUsers,
-            SparseArray<ArraySet<ComponentName>> approvedComponentsByUser,
-            boolean isVisibleBackgroundUser) {
-        // When it is a visible background user in Automotive MUMD environment,
-        // don't clear mEnabledServicesForCurrentProfile and mEnabledServicesPackageNames.
-        if (!isVisibleBackgroundUser) {
-            mEnabledServicesForCurrentProfiles.clear();
-            mEnabledServicesPackageNames.clear();
-        }
+            SparseArray<ArraySet<ComponentName>> approvedComponentsByUser) {
+        mEnabledServicesForCurrentProfiles.clear();
+        mEnabledServicesPackageNames.clear();
         final int nUserIds = activeUsers.size();
 
         for (int i = 0; i < nUserIds; ++i) {
@@ -1417,12 +1408,7 @@
             }
 
             componentsToBind.put(userId, add);
-            // When it is a visible background user in Automotive MUMD environment,
-            // skip adding items to mEnabledServicesForCurrentProfile
-            // and mEnabledServicesPackageNames.
-            if (isVisibleBackgroundUser) {
-                continue;
-            }
+
             mEnabledServicesForCurrentProfiles.addAll(userComponents);
 
             for (int j = 0; j < userComponents.size(); j++) {
@@ -1470,10 +1456,7 @@
         IntArray userIds = mUserProfiles.getCurrentProfileIds();
         boolean rebindAllCurrentUsers = mUserProfiles.isProfileUser(userToRebind, mContext)
                 && allowRebindForParentUser();
-        boolean isVisibleBackgroundUser = false;
         if (userToRebind != USER_ALL && !rebindAllCurrentUsers) {
-            isVisibleBackgroundUser =
-                    mUserManagerInternal.isVisibleBackgroundFullUser(userToRebind);
             userIds = new IntArray(1);
             userIds.add(userToRebind);
         }
@@ -1488,8 +1471,7 @@
 
             // Filter approvedComponentsByUser to collect all of the components that are allowed
             // for the currently active user(s).
-            populateComponentsToBind(componentsToBind, userIds, approvedComponentsByUser,
-                    isVisibleBackgroundUser);
+            populateComponentsToBind(componentsToBind, userIds, approvedComponentsByUser);
 
             // For every current non-system connection, disconnect services that are no longer
             // approved, or ALL services if we are force rebinding
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 845798f..8d039f1 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5859,8 +5859,8 @@
     public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
             long whenNanos, int policyFlags) {
         if ((policyFlags & FLAG_WAKE) != 0) {
-            if (mWindowWakeUpPolicy.wakeUpFromMotion(
-                        whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
+            if (mWindowWakeUpPolicy.wakeUpFromMotion(displayId, whenNanos / 1000000, source,
+                    action == MotionEvent.ACTION_DOWN)) {
                 // Woke up. Pass motion events to user.
                 return ACTION_PASS_TO_USER;
             }
@@ -5874,8 +5874,8 @@
         // there will be no dream to intercept the touch and wake into ambient.  The device should
         // wake up in this case.
         if (isTheaterModeEnabled() && (policyFlags & FLAG_WAKE) != 0) {
-            if (mWindowWakeUpPolicy.wakeUpFromMotion(
-                        whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
+            if (mWindowWakeUpPolicy.wakeUpFromMotion(displayId, whenNanos / 1000000, source,
+                    action == MotionEvent.ACTION_DOWN)) {
                 // Woke up. Pass motion events to user.
                 return ACTION_PASS_TO_USER;
             }
@@ -6223,7 +6223,7 @@
     }
 
     private void wakeUpFromWakeKey(long eventTime, int keyCode, boolean isDown) {
-        if (mWindowWakeUpPolicy.wakeUpFromKey(eventTime, keyCode, isDown)) {
+        if (mWindowWakeUpPolicy.wakeUpFromKey(DEFAULT_DISPLAY, eventTime, keyCode, isDown)) {
             final boolean keyCanLaunchHome = keyCode == KEYCODE_HOME || keyCode == KEYCODE_POWER;
             // Start HOME with "reason" extra if sleeping for more than mWakeUpToLastStateTimeout
             if (shouldWakeUpWithHomeIntent() &&  keyCanLaunchHome) {
diff --git a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
index af1ad13..04dbd1f 100644
--- a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
@@ -25,6 +25,7 @@
 import static android.view.KeyEvent.KEYCODE_POWER;
 
 import static com.android.server.policy.Flags.supportInputWakeupDelegate;
+import static com.android.server.power.feature.flags.Flags.perDisplayWakeByTouch;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -107,13 +108,14 @@
     /**
      * Wakes up from a key event.
      *
+     * @param displayId the id of the display to wake.
      * @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
      * @param keyCode the {@link android.view.KeyEvent} key code of the key event.
      * @param isDown {@code true} if the event's action is {@link KeyEvent#ACTION_DOWN}.
      * @return {@code true} if the policy allows the requested wake up and the request has been
      *      executed; {@code false} otherwise.
      */
-    boolean wakeUpFromKey(long eventTime, int keyCode, boolean isDown) {
+    boolean wakeUpFromKey(int displayId, long eventTime, int keyCode, boolean isDown) {
         final boolean wakeAllowedDuringTheaterMode =
                 keyCode == KEYCODE_POWER
                         ? mAllowTheaterModeWakeFromPowerKey
@@ -126,22 +128,31 @@
                 && mInputWakeUpDelegate.wakeUpFromKey(eventTime, keyCode, isDown)) {
             return true;
         }
-        wakeUp(
-                eventTime,
-                keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
-                keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+        if (perDisplayWakeByTouch()) {
+            wakeUp(
+                    displayId,
+                    eventTime,
+                    keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
+                    keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+        } else {
+            wakeUp(
+                    eventTime,
+                    keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
+                    keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+        }
         return true;
     }
 
     /**
      * Wakes up from a motion event.
      *
+     * @param displayId the id of the display to wake.
      * @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
      * @param isDown {@code true} if the event's action is {@link MotionEvent#ACTION_DOWN}.
      * @return {@code true} if the policy allows the requested wake up and the request has been
      *      executed; {@code false} otherwise.
      */
-    boolean wakeUpFromMotion(long eventTime, int source, boolean isDown) {
+    boolean wakeUpFromMotion(int displayId, long eventTime, int source, boolean isDown) {
         if (!canWakeUp(mAllowTheaterModeWakeFromMotion)) {
             if (DEBUG) Slog.d(TAG, "Unable to wake up from motion.");
             return false;
@@ -150,7 +161,11 @@
                 && mInputWakeUpDelegate.wakeUpFromMotion(eventTime, source, isDown)) {
             return true;
         }
-        wakeUp(eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
+        if (perDisplayWakeByTouch()) {
+            wakeUp(displayId, eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
+        } else {
+            wakeUp(eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
+        }
         return true;
     }
 
@@ -237,4 +252,12 @@
     private void wakeUp(long wakeTime, @WakeReason int reason, String details) {
         mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details);
     }
+
+    /** Wakes up given display. */
+    private void wakeUp(int displayId, long wakeTime, @WakeReason int reason, String details) {
+        // If we're given an invalid display id to wake, fall back to waking default display
+        final int displayIdToWake =
+                displayId == Display.INVALID_DISPLAY ? Display.DEFAULT_DISPLAY : displayId;
+        mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details, displayIdToWake);
+    }
 }
diff --git a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java b/services/core/java/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationService.java
similarity index 96%
rename from services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java
rename to services/core/java/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationService.java
index 9398c7a..b129fdc 100644
--- a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java
+++ b/services/core/java/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationService.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.adaptiveauth;
+package com.android.server.security.adaptiveauthentication;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
 
@@ -55,8 +55,8 @@
 /**
  * @hide
  */
-public class AdaptiveAuthService extends SystemService {
-    private static final String TAG = "AdaptiveAuthService";
+public class AdaptiveAuthenticationService extends SystemService {
+    private static final String TAG = "AdaptiveAuthenticationService";
     private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
 
     @VisibleForTesting
@@ -78,12 +78,12 @@
     final SparseIntArray mFailedAttemptsForUser = new SparseIntArray();
     private final SparseLongArray mLastLockedTimestamp = new SparseLongArray();
 
-    public AdaptiveAuthService(Context context) {
+    public AdaptiveAuthenticationService(Context context) {
         this(context, new LockPatternUtils(context));
     }
 
     @VisibleForTesting
-    public AdaptiveAuthService(Context context, LockPatternUtils lockPatternUtils) {
+    public AdaptiveAuthenticationService(Context context, LockPatternUtils lockPatternUtils) {
         super(context);
         mLockPatternUtils = lockPatternUtils;
         mLockSettings = Objects.requireNonNull(
diff --git a/services/core/java/com/android/server/security/adaptiveauthentication/OWNERS b/services/core/java/com/android/server/security/adaptiveauthentication/OWNERS
new file mode 100644
index 0000000..29affcd
--- /dev/null
+++ b/services/core/java/com/android/server/security/adaptiveauthentication/OWNERS
@@ -0,0 +1,3 @@
+hainingc@google.com
+jbolinger@google.com
+graciecheng@google.com
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index ec171c5..a71620d 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -46,10 +46,8 @@
 import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
 import static com.android.window.flags.Flags.balAdditionalStartModes;
 import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg;
-import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
 import static com.android.window.flags.Flags.balImprovedMetrics;
 import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
-import static com.android.window.flags.Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid;
 import static com.android.window.flags.Flags.balShowToastsBlocked;
 import static com.android.window.flags.Flags.balStrictModeRo;
 
@@ -348,11 +346,7 @@
             @BackgroundActivityStartMode int realCallerBackgroundActivityStartMode =
                     checkedOptions.getPendingIntentBackgroundActivityStartMode();
 
-            if (!balImproveRealCallerVisibilityCheck()) {
-                // without this fix the auto-opt ins below would violate CTS tests
-                mAutoOptInReason = null;
-                mAutoOptInCaller = false;
-            } else if (originatingPendingIntent == null) {
+            if (originatingPendingIntent == null) {
                 mAutoOptInReason = AUTO_OPT_IN_NOT_PENDING_INTENT;
                 mAutoOptInCaller = true;
             } else if (mIsCallForResult) {
@@ -599,12 +593,8 @@
                         mCheckedOptions.getPendingIntentBackgroundActivityStartMode()));
             }
             // features
-            sb.append("; balImproveRealCallerVisibilityCheck: ")
-                    .append(balImproveRealCallerVisibilityCheck());
             sb.append("; balRequireOptInByPendingIntentCreator: ")
                     .append(balRequireOptInByPendingIntentCreator());
-            sb.append("; balRespectAppSwitchStateWhenCheckBoundByForegroundUid: ")
-                    .append(balRespectAppSwitchStateWhenCheckBoundByForegroundUid());
             sb.append("; balDontBringExistingBackgroundTaskStackToFg: ")
                     .append(balDontBringExistingBackgroundTaskStackToFg());
             sb.append("]");
@@ -1133,23 +1123,13 @@
         final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW
                 || state.mAppSwitchState == APP_SWITCH_FG_ONLY
                 || isHomeApp(state.mRealCallingUid, state.mRealCallingPackage);
-        if (balImproveRealCallerVisibilityCheck()) {
-            if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) {
-                return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
-                        /*background*/ false, "realCallingUid has visible window");
-            }
-            if (mService.mActiveUids.hasNonAppVisibleWindow(state.mRealCallingUid)) {
-                return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
-                        /*background*/ false, "realCallingUid has non-app visible window");
-            }
-        } else {
-            // don't abort if the realCallingUid has a visible window
-            // TODO(b/171459802): We should check appSwitchAllowed also
-            if (state.mRealCallingUidHasAnyVisibleWindow) {
-                return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
-                        /*background*/ false,
-                        "realCallingUid has visible (non-toast) window.");
-            }
+        if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) {
+            return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
+                    /*background*/ false, "realCallingUid has visible window");
+        }
+        if (mService.mActiveUids.hasNonAppVisibleWindow(state.mRealCallingUid)) {
+            return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
+                    /*background*/ false, "realCallingUid has non-app visible window");
         }
 
         // Don't abort if the realCallerApp or other processes of that uid are considered to be in
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 264c8be..ccf1aed 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -50,7 +50,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
-import com.android.window.flags.Flags;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -137,10 +136,8 @@
         }
         // Allow if the caller is bound by a UID that's currently foreground.
         // But still respect the appSwitchState.
-        if (checkConfiguration.checkVisibility && (
-                Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid()
-                        ? appSwitchState != APP_SWITCH_DISALLOW && isBoundByForegroundUid()
-                        : isBoundByForegroundUid())) {
+        if (checkConfiguration.checkVisibility && appSwitchState != APP_SWITCH_DISALLOW
+                && isBoundByForegroundUid()) {
             return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_BOUND_BY_FOREGROUND
                     : BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
                     "process bound by foreground uid");
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 221b848..9759772 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -119,7 +119,6 @@
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accounts.AccountManagerService;
-import com.android.server.adaptiveauth.AdaptiveAuthService;
 import com.android.server.adb.AdbService;
 import com.android.server.alarm.AlarmManagerService;
 import com.android.server.am.ActivityManagerService;
@@ -250,6 +249,7 @@
 import com.android.server.security.FileIntegrityService;
 import com.android.server.security.KeyAttestationApplicationIdProviderService;
 import com.android.server.security.KeyChainSystemService;
+import com.android.server.security.adaptiveauthentication.AdaptiveAuthenticationService;
 import com.android.server.security.advancedprotection.AdvancedProtectionService;
 import com.android.server.security.rkp.RemoteProvisioningService;
 import com.android.server.selinux.SelinuxAuditLogsService;
@@ -2651,8 +2651,8 @@
             t.traceEnd();
 
             if (android.adaptiveauth.Flags.enableAdaptiveAuth()) {
-                t.traceBegin("StartAdaptiveAuthService");
-                mSystemServiceManager.startService(AdaptiveAuthService.class);
+                t.traceBegin("StartAdaptiveAuthenticationService");
+                mSystemServiceManager.startService(AdaptiveAuthenticationService.class);
                 t.traceEnd();
             }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index dcbc234..2a825f3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -1308,8 +1308,6 @@
         Intent intent = new Intent();
         Intent extraIntent = new Intent("EXTRA_INTENT_ACTION");
         intent.putExtra("EXTRA_INTENT0", extraIntent);
-        Intent nestedIntent = new Intent("NESTED_INTENT_ACTION");
-        extraIntent.putExtra("NESTED_INTENT", nestedIntent);
 
         intent.collectExtraIntentKeys();
         mAms.addCreatorToken(intent, TEST_PACKAGE);
@@ -1319,11 +1317,6 @@
         assertThat(token).isNotNull();
         assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid());
         assertThat(token.getCreatorPackage()).isEqualTo(TEST_PACKAGE);
-
-        token = (ActivityManagerService.IntentCreatorToken) nestedIntent.getCreatorToken();
-        assertThat(token).isNotNull();
-        assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid());
-        assertThat(token.getCreatorPackage()).isEqualTo(TEST_PACKAGE);
     }
 
     @Test
@@ -1356,8 +1349,6 @@
         Intent intent = new Intent();
         Intent extraIntent = new Intent("EXTRA_INTENT_ACTION");
         intent.putExtra("EXTRA_INTENT", extraIntent);
-        Intent nestedIntent = new Intent("NESTED_INTENT_ACTION");
-        extraIntent.putExtra("NESTED_INTENT", nestedIntent);
 
         intent.collectExtraIntentKeys();
 
@@ -1383,12 +1374,9 @@
         extraIntent = intent.getParcelableExtra("EXTRA_INTENT", Intent.class);
         extraIntent2 = intent.getParcelableExtra("EXTRA_INTENT2", Intent.class);
         extraIntent3 = intent.getParcelableExtra("EXTRA_INTENT3", Intent.class);
-        nestedIntent = extraIntent.getParcelableExtra("NESTED_INTENT", Intent.class);
 
         assertThat(extraIntent.getExtendedFlags()
                 & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0);
-        assertThat(nestedIntent.getExtendedFlags()
-                & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0);
         // sneaked in intent should have EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN set.
         assertThat(extraIntent2.getExtendedFlags()
                 & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isNotEqualTo(0);
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index c645c08..9b7bbe0 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -114,6 +114,7 @@
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" />
+    <uses-permission android:name="android.permission.MANAGE_KEY_GESTURES" />
 
     <queries>
         <package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 8164ef9..f0d3456 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -19,9 +19,13 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
 
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MAX_VALUE;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MIN_VALUE;
 import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID;
 import static com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -660,6 +664,90 @@
     }
 
     @Test
+    public void scaleMagnificationByStep_fullscreenMode_stepInAndOut() throws RemoteException {
+        setMagnificationEnabled(MODE_FULLSCREEN);
+        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 1.0f, false);
+        reset(mScreenMagnificationController);
+
+        // Verify the zoom scale factor increases by
+        // {@code MagnificationController.DefaultMagnificationScaleStepProvider
+        // .ZOOM_STEP_SCALE_FACTOR} and the center coordinates are
+        // unchanged (Float.NaN as values denotes unchanged center).
+        mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+                MagnificationController.ZOOM_DIRECTION_IN);
+        verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY),
+                eq(MagnificationController
+                        .DefaultMagnificationScaleStepProvider.ZOOM_STEP_SCALE_FACTOR),
+                eq(Float.NaN), eq(Float.NaN), anyBoolean(), anyInt());
+
+        mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+                MagnificationController.ZOOM_DIRECTION_OUT);
+        verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY),
+                eq(SCALE_MIN_VALUE), eq(Float.NaN), eq(Float.NaN), anyBoolean(), anyInt());
+    }
+
+    @Test
+    public void scaleMagnificationByStep_testMaxScaling() throws RemoteException {
+        setMagnificationEnabled(MODE_FULLSCREEN);
+        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, SCALE_MIN_VALUE, false);
+        reset(mScreenMagnificationController);
+
+        float currentScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+        while (currentScale < SCALE_MAX_VALUE) {
+            assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+                    MagnificationController.ZOOM_DIRECTION_IN)).isTrue();
+            final float nextScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+            assertThat(nextScale).isGreaterThan(currentScale);
+            currentScale = nextScale;
+        }
+
+        assertThat(currentScale).isEqualTo(SCALE_MAX_VALUE);
+        assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+                MagnificationController.ZOOM_DIRECTION_IN)).isFalse();
+    }
+
+    @Test
+    public void scaleMagnificationByStep_testMinScaling() throws RemoteException {
+        setMagnificationEnabled(MODE_FULLSCREEN);
+        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, SCALE_MAX_VALUE, false);
+        reset(mScreenMagnificationController);
+
+        float currentScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+        while (currentScale > SCALE_MIN_VALUE) {
+            assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+                    MagnificationController.ZOOM_DIRECTION_OUT)).isTrue();
+            final float nextScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+            assertThat(nextScale).isLessThan(currentScale);
+            currentScale = nextScale;
+        }
+
+        assertThat(currentScale).isEqualTo(SCALE_MIN_VALUE);
+        assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+                MagnificationController.ZOOM_DIRECTION_OUT)).isFalse();
+    }
+
+    @Test
+    public void scaleMagnificationByStep_windowedMode_stepInAndOut() throws RemoteException {
+        setMagnificationEnabled(MODE_WINDOW);
+        mMagnificationController.onPerformScaleAction(TEST_DISPLAY, SCALE_MIN_VALUE, false);
+        reset(mMagnificationConnectionManager);
+
+        // Verify the zoom scale factor increases by
+        // {@code MagnificationController.DefaultMagnificationScaleStepProvider
+        // .ZOOM_STEP_SCALE_FACTOR}.
+        mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+                MagnificationController.ZOOM_DIRECTION_IN);
+        verify(mMagnificationConnectionManager).setScale(eq(TEST_DISPLAY),
+                eq(MagnificationController
+                        .DefaultMagnificationScaleStepProvider.ZOOM_STEP_SCALE_FACTOR));
+
+        mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+                MagnificationController.ZOOM_DIRECTION_OUT);
+        verify(mMagnificationConnectionManager).setScale(eq(TEST_DISPLAY),
+                eq(SCALE_MIN_VALUE));
+    }
+
+    @Test
     public void enableWindowMode_notifyMagnificationChanged() throws RemoteException {
         setMagnificationEnabled(MODE_WINDOW);
 
diff --git a/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS b/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS
deleted file mode 100644
index 0218a78..0000000
--- a/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/adaptiveauth/OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index bc410d9..88829c1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -62,6 +62,7 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.BiometricStateListener;
 import android.hardware.biometrics.Flags;
 import android.hardware.biometrics.IBiometricAuthenticator;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
@@ -70,8 +71,16 @@
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.SensorProperties;
 import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
 import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
 import android.hardware.keymaster.HardwareAuthenticatorType;
 import android.os.Binder;
 import android.os.Handler;
@@ -84,6 +93,7 @@
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
 import android.security.GateKeeper;
 import android.security.KeyStoreAuthorization;
 import android.service.gatekeeper.IGateKeeperService;
@@ -93,6 +103,7 @@
 import android.view.DisplayInfo;
 import android.view.WindowManager;
 
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
@@ -111,6 +122,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
 
@@ -144,6 +156,27 @@
 
     private static final int SENSOR_ID_FINGERPRINT = 0;
     private static final int SENSOR_ID_FACE = 1;
+    private final ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback.Stub>
+            mFingerprintAuthenticatorRegisteredCallbackCaptor = ArgumentCaptor.forClass(
+            IFingerprintAuthenticatorsRegisteredCallback.Stub.class);
+    private final ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback.Stub>
+            mFaceAuthenticatorRegisteredCallbackCaptor = ArgumentCaptor.forClass(
+            IFaceAuthenticatorsRegisteredCallback.Stub.class);
+    private final ArgumentCaptor<BiometricStateListener> mBiometricStateListenerArgumentCaptor =
+            ArgumentCaptor.forClass(BiometricStateListener.class);
+    private final List<FingerprintSensorPropertiesInternal>
+            mFingerprintSensorPropertiesInternals = List.of(
+                    new FingerprintSensorPropertiesInternal(SENSOR_ID_FINGERPRINT,
+                            SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+                            List.of(), FingerprintSensorProperties.TYPE_UNKNOWN,
+                            true /* resetLockoutRequiresHardwareAuthToken */));
+    private final List<FaceSensorPropertiesInternal>
+            mFaceSensorPropertiesInternals = List.of(
+                    new FaceSensorPropertiesInternal(SENSOR_ID_FACE,
+                            SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+                            List.of(), FaceSensorProperties.TYPE_UNKNOWN,
+                            false /* supportsFaceDetection */, false /* supportsSelfIllumination */,
+                            false /* resetLockoutRequiresChallenge */));
 
     private BiometricService mBiometricService;
 
@@ -192,6 +225,10 @@
 
     @Mock
     private BiometricNotificationLogger mNotificationLogger;
+    @Mock
+    private FingerprintManager mFingerprintManager;
+    @Mock
+    private FaceManager mFaceManager;
 
     BiometricContextProvider mBiometricContextProvider;
 
@@ -1975,6 +2012,59 @@
                 eq(hardwareAuthenticators));
     }
 
+    @Test
+    public void testMandatoryBiometricsValue_whenParentProfileEnabled() throws RemoteException {
+        final Context context = ApplicationProvider.getApplicationContext();
+        final int profileParentId = context.getContentResolver().getUserId();
+        final int userId = profileParentId + 1;
+        final BiometricService.SettingObserver settingObserver =
+                new BiometricService.SettingObserver(
+                        context, mBiometricHandlerProvider.getBiometricCallbackHandler(),
+                        new ArrayList<>(), mUserManager, mFingerprintManager, mFaceManager);
+
+        verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
+                mFingerprintAuthenticatorRegisteredCallbackCaptor.capture());
+        verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+                mFaceAuthenticatorRegisteredCallbackCaptor.capture());
+
+        mFingerprintAuthenticatorRegisteredCallbackCaptor.getValue().onAllAuthenticatorsRegistered(
+                mFingerprintSensorPropertiesInternals);
+        mFaceAuthenticatorRegisteredCallbackCaptor.getValue().onAllAuthenticatorsRegistered(
+                mFaceSensorPropertiesInternals);
+
+        verify(mFingerprintManager).registerBiometricStateListener(
+                mBiometricStateListenerArgumentCaptor.capture());
+
+        mBiometricStateListenerArgumentCaptor.getValue().onEnrollmentsChanged(userId,
+                SENSOR_ID_FINGERPRINT, true /* hasEnrollments */);
+
+        verify(mFaceManager).registerBiometricStateListener(
+                mBiometricStateListenerArgumentCaptor.capture());
+
+        mBiometricStateListenerArgumentCaptor.getValue().onEnrollmentsChanged(userId,
+                SENSOR_ID_FACE, true /* hasEnrollments */);
+
+        when(mUserManager.getProfileParent(userId)).thenReturn(new UserInfo(profileParentId,
+                "", 0));
+        when(mUserManager.getEnabledProfileIds(profileParentId)).thenReturn(new int[]{userId});
+
+        //Disable Identity Check for profile user
+        Settings.Secure.putIntForUser(context.getContentResolver(),
+                Settings.Secure.MANDATORY_BIOMETRICS, 0, userId);
+        Settings.Secure.putIntForUser(context.getContentResolver(),
+                Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, 0,
+                userId);
+        //Enable Identity Check for parent user
+        Settings.Secure.putIntForUser(context.getContentResolver(),
+                Settings.Secure.MANDATORY_BIOMETRICS, 1, profileParentId);
+        Settings.Secure.putIntForUser(context.getContentResolver(),
+                Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, 1,
+                profileParentId);
+
+        assertTrue(settingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
+                userId));
+    }
+
     // Helper methods
 
     private int invokeCanAuthenticate(BiometricService service, int authenticators)
diff --git a/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationServiceTest.java
similarity index 94%
rename from services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java
rename to services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationServiceTest.java
index d180688..154494a 100644
--- a/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationServiceTest.java
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.server.adaptiveauth;
+package com.android.server.security.adaptiveauthentication;
 
 import static android.adaptiveauth.Flags.FLAG_ENABLE_ADAPTIVE_AUTH;
 import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
 import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
-import static com.android.server.adaptiveauth.AdaptiveAuthService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS;
+import static com.android.server.security.adaptiveauthentication.AdaptiveAuthenticationService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
@@ -66,12 +66,12 @@
 import org.mockito.MockitoAnnotations;
 
 /**
- * atest FrameworksServicesTests:AdaptiveAuthServiceTest
+ * atest FrameworksServicesTests:AdaptiveAuthenticationServiceTest
  */
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class AdaptiveAuthServiceTest {
+public class AdaptiveAuthenticationServiceTest {
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
@@ -81,7 +81,7 @@
     private static final int REASON_UNKNOWN = 0; // BiometricRequestConstants.RequestReason
 
     private Context mContext;
-    private AdaptiveAuthService mAdaptiveAuthService;
+    private AdaptiveAuthenticationService mAdaptiveAuthenticationService;
 
     @Mock
     LockPatternUtils mLockPatternUtils;
@@ -124,8 +124,9 @@
         LocalServices.removeServiceForTest(UserManagerInternal.class);
         LocalServices.addService(UserManagerInternal.class, mUserManager);
 
-        mAdaptiveAuthService = new AdaptiveAuthService(mContext, mLockPatternUtils);
-        mAdaptiveAuthService.init();
+        mAdaptiveAuthenticationService = new AdaptiveAuthenticationService(
+                mContext, mLockPatternUtils);
+        mAdaptiveAuthenticationService.init();
 
         verify(mLockSettings).registerLockSettingsStateListener(
                 mLockSettingsStateListenerCaptor.capture());
@@ -317,13 +318,13 @@
 
     private void verifyNotLockDevice(int expectedCntFailedAttempts, int userId) {
         assertEquals(expectedCntFailedAttempts,
-                mAdaptiveAuthService.mFailedAttemptsForUser.get(userId));
+                mAdaptiveAuthenticationService.mFailedAttemptsForUser.get(userId));
         verify(mWindowManager, never()).lockNow();
     }
 
     private void verifyLockDevice(int userId) {
         assertEquals(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS,
-                mAdaptiveAuthService.mFailedAttemptsForUser.get(userId));
+                mAdaptiveAuthenticationService.mFailedAttemptsForUser.get(userId));
         verify(mLockPatternUtils).requireStrongAuth(
                 eq(SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST), eq(userId));
         // If userId is MANAGED_PROFILE_USER_ID, the StrongAuthFlag of its parent (PRIMARY_USER_ID)
diff --git a/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/OWNERS b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/OWNERS
new file mode 100644
index 0000000..bc8efa9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/security/adaptiveauthentication/OWNERS
\ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
index c9d5241..b3ec215 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
@@ -30,7 +30,6 @@
 
 import androidx.test.InstrumentationRegistry;
 
-import com.android.server.pm.UserManagerInternal;
 import com.android.server.uri.UriGrantsManagerInternal;
 
 import org.junit.After;
@@ -42,7 +41,6 @@
 
 public class UiServiceTestCase {
     @Mock protected PackageManagerInternal mPmi;
-    @Mock protected UserManagerInternal mUmi;
     @Mock protected UriGrantsManagerInternal mUgmInternal;
 
     protected static final String PKG_N_MR1 = "com.example.n_mr1";
@@ -94,8 +92,6 @@
                     }
                 });
 
-        LocalServices.removeServiceForTest(UserManagerInternal.class);
-        LocalServices.addService(UserManagerInternal.class, mUmi);
         LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
         LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal);
         when(mUgmInternal.checkGrantUriPermission(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 38ff3a2..cc02865 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -2410,11 +2410,13 @@
                 NotificationChannel.NEWS_ID);
         for (NotificationRecord record: notificationList) {
             if (record.getChannel().getId().equals(channel1.getId())
+                    && record.getNotification().isGroupChild()
                     && record.getSbn().getId() % 2 == 0) {
                 record.updateNotificationChannel(socialChannel);
                 mGroupHelper.onChannelUpdated(record);
             }
             if (record.getChannel().getId().equals(channel1.getId())
+                    && record.getNotification().isGroupChild()
                     && record.getSbn().getId() % 2 != 0) {
                 record.updateNotificationChannel(newsChannel);
                 mGroupHelper.onChannelUpdated(record);
@@ -2474,7 +2476,8 @@
                 NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
                 IMPORTANCE_DEFAULT);
         for (NotificationRecord record: notificationList) {
-            if (record.getChannel().getId().equals(channel1.getId())) {
+            if (record.getChannel().getId().equals(channel1.getId())
+                    && record.getNotification().isGroupChild()) {
                 record.updateNotificationChannel(socialChannel);
                 mGroupHelper.onChannelUpdated(record);
             }
@@ -2532,7 +2535,8 @@
                 BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
                 NotificationChannel.SOCIAL_MEDIA_ID);
         for (NotificationRecord record: notificationList) {
-            if (record.getOriginalGroupKey().contains("testGrp")) {
+            if (record.getOriginalGroupKey().contains("testGrp")
+                    && record.getNotification().isGroupChild()) {
                 record.updateNotificationChannel(socialChannel);
                 mGroupHelper.onChannelUpdated(record);
             }
@@ -2631,7 +2635,8 @@
                 BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
                 NotificationChannel.SOCIAL_MEDIA_ID);
         for (NotificationRecord record: notificationList) {
-            if (record.getOriginalGroupKey().contains("testGrp")) {
+            if (record.getOriginalGroupKey().contains("testGrp")
+                    && record.getNotification().isGroupChild()) {
                 record.updateNotificationChannel(socialChannel);
                 mGroupHelper.onChannelUpdated(record);
             }
@@ -2650,6 +2655,64 @@
     }
 
     @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+            FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
+            FLAG_NOTIFICATION_CLASSIFICATION})
+    public void testValidGroupsRegrouped_notificationBundledWhileEnqueued() {
+        // Check that valid group notifications are regrouped if classification is done
+        // before onNotificationPostedWithDelay (within DELAY_FOR_ASSISTANT_TIME)
+        final List<NotificationRecord> notificationList = new ArrayList<>();
+        final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+        final String pkg = "package";
+
+        final int summaryId = 0;
+        final int numChildren = 3;
+        // Post a regular/valid group: summary + notifications
+        NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+                String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp", true);
+        notificationList.add(summary);
+        summaryByGroup.put(summary.getGroupKey(), summary);
+        for (int i = 0; i < numChildren; i++) {
+            NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+                    UserHandle.SYSTEM, "testGrp", false);
+            notificationList.add(child);
+        }
+
+        // Classify/bundle child notifications. Don't call onChannelUpdated,
+        // adjustments applied while enqueued will use NotificationAdjustmentExtractor.
+        final NotificationChannel socialChannel = new NotificationChannel(
+                NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+                IMPORTANCE_DEFAULT);
+        final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
+                AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
+        final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
+                BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+                NotificationChannel.SOCIAL_MEDIA_ID);
+        for (NotificationRecord record: notificationList) {
+            if (record.getOriginalGroupKey().contains("testGrp")
+                    && record.getNotification().isGroupChild()) {
+                record.updateNotificationChannel(socialChannel);
+            }
+        }
+
+        // Check that notifications are forced grouped and app-provided summaries are canceled
+        for (NotificationRecord record: notificationList) {
+            mGroupHelper.onNotificationPostedWithDelay(record, notificationList, summaryByGroup);
+        }
+
+        verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+                eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
+        verify(mCallback, times(numChildren)).addAutoGroup(anyString(), eq(expectedGroupKey_social),
+                eq(true));
+        verify(mCallback, never()).removeAutoGroup(anyString());
+        verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+        verify(mCallback, times(numChildren - 1)).updateAutogroupSummary(anyInt(), anyString(),
+                anyString(), any());
+        verify(mCallback, times(numChildren)).removeAppProvidedSummaryOnClassification(anyString(),
+                anyString());
+    }
+
+    @Test
     @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
     public void testMoveAggregateGroups_updateChannel_groupsUngrouped() {
         final String pkg = "package";
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 48bc9d7..e5c42082 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -1484,7 +1484,6 @@
         assertTrue(componentsToUnbind.get(0).contains(ComponentName.unflattenFromString("c/c")));
     }
 
-    @SuppressWarnings("GuardedBy")
     @Test
     public void populateComponentsToBind() {
         ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
@@ -1508,8 +1507,7 @@
 
         SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>();
 
-        service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser,
-                /* isVisibleBackgroundUser= */ false);
+        service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser);
 
         assertEquals(2, componentsToBind.size());
         assertEquals(1, componentsToBind.get(0).size());
@@ -1519,33 +1517,6 @@
         assertTrue(componentsToBind.get(10).contains(ComponentName.unflattenFromString("c/c")));
     }
 
-    @SuppressWarnings("GuardedBy")
-    @Test
-    public void populateComponentsToBind_isVisibleBackgroundUser_addComponentsToBindButNotAddToEnabledComponent() {
-        ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
-                APPROVAL_BY_COMPONENT);
-
-        SparseArray<ArraySet<ComponentName>> approvedComponentsByUser = new SparseArray<>();
-        ArraySet<ComponentName> allowed = new ArraySet<>();
-        allowed.add(ComponentName.unflattenFromString("pkg1/cmp1"));
-        approvedComponentsByUser.put(11, allowed);
-        IntArray users = new IntArray();
-        users.add(11);
-
-        SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>();
-
-        service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser,
-                /* isVisibleBackgroundUser= */ true);
-
-        assertEquals(1, componentsToBind.size());
-        assertEquals(1, componentsToBind.get(11).size());
-        assertTrue(componentsToBind.get(11).contains(ComponentName.unflattenFromString(
-                "pkg1/cmp1")));
-        assertThat(service.isComponentEnabledForCurrentProfiles(
-                new ComponentName("pkg1", "cmp1"))).isFalse();
-        assertThat(service.isComponentEnabledForPackage("pkg1")).isFalse();
-    }
-
     @Test
     public void testOnNullBinding() throws Exception {
         Context context = mock(Context.class);
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 6596ee9..a51ce995 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -699,8 +699,8 @@
 
     void assertPowerWakeUp() {
         mTestLooper.dispatchAll();
-        verify(mWindowWakeUpPolicy)
-                .wakeUpFromKey(anyLong(), eq(KeyEvent.KEYCODE_POWER), anyBoolean());
+        verify(mWindowWakeUpPolicy).wakeUpFromKey(
+                eq(DEFAULT_DISPLAY), anyLong(), eq(KeyEvent.KEYCODE_POWER), anyBoolean());
     }
 
     void assertNoPowerSleep() {
diff --git a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
index 7322e5a..3ca352c 100644
--- a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
@@ -22,6 +22,7 @@
 import static android.os.PowerManager.WAKE_REASON_POWER_BUTTON;
 import static android.os.PowerManager.WAKE_REASON_WAKE_KEY;
 import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.InputDevice.SOURCE_ROTARY_ENCODER;
 import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
 import static android.view.KeyEvent.KEYCODE_HOME;
@@ -35,6 +36,7 @@
 import static com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch;
 import static com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture;
 import static com.android.server.policy.Flags.FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE;
+import static com.android.server.power.feature.flags.Flags.FLAG_PER_DISPLAY_WAKE_BY_TOUCH;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -43,6 +45,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -52,6 +55,8 @@
 import android.content.ContextWrapper;
 import android.content.res.Resources;
 import android.os.PowerManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.view.Display;
@@ -125,6 +130,7 @@
     }
 
     @Test
+    @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
     public void testMotionWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
         setTheaterModeEnabled(false);
         mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
@@ -136,7 +142,8 @@
 
         // Verify the policy wake up call succeeds because of the call on the delegate, and not
         // because of a PowerManager wake up.
-        assertThat(mPolicy.wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true)).isTrue();
+        assertThat(mPolicy.wakeUpFromMotion(
+                mDefaultDisplay.getDisplayId(), 200, SOURCE_TOUCHSCREEN, true)).isTrue();
         verify(mInputWakeUpDelegate).wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true);
         verifyNoPowerManagerWakeUp();
 
@@ -144,12 +151,14 @@
 
         // Verify the policy wake up call succeeds because of the PowerManager wake up, since the
         // delegate would not handle the wake up request.
-        assertThat(mPolicy.wakeUpFromMotion(300, SOURCE_ROTARY_ENCODER, false)).isTrue();
+        assertThat(mPolicy.wakeUpFromMotion(
+                mDefaultDisplay.getDisplayId(), 300, SOURCE_ROTARY_ENCODER, false)).isTrue();
         verify(mInputWakeUpDelegate).wakeUpFromMotion(300, SOURCE_ROTARY_ENCODER, false);
         verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
     }
 
     @Test
+    @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
     public void testKeyWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
         setTheaterModeEnabled(false);
         mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
@@ -161,7 +170,7 @@
 
         // Verify the policy wake up call succeeds because of the call on the delegate, and not
         // because of a PowerManager wake up.
-        assertThat(mPolicy.wakeUpFromKey(200, KEYCODE_POWER, true)).isTrue();
+        assertThat(mPolicy.wakeUpFromKey(DEFAULT_DISPLAY, 200, KEYCODE_POWER, true)).isTrue();
         verify(mInputWakeUpDelegate).wakeUpFromKey(200, KEYCODE_POWER, true);
         verifyNoPowerManagerWakeUp();
 
@@ -169,7 +178,8 @@
 
         // Verify the policy wake up call succeeds because of the PowerManager wake up, since the
         // delegate would not handle the wake up request.
-        assertThat(mPolicy.wakeUpFromKey(300, KEYCODE_STEM_PRIMARY, false)).isTrue();
+        assertThat(mPolicy.wakeUpFromKey(
+                DEFAULT_DISPLAY, 300, KEYCODE_STEM_PRIMARY, false)).isTrue();
         verify(mInputWakeUpDelegate).wakeUpFromKey(300, KEYCODE_STEM_PRIMARY, false);
         verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_KEY, "android.policy:KEY");
     }
@@ -186,7 +196,7 @@
                 .setInputWakeUpDelegate(mInputWakeUpDelegate);
 
         // Check that the wake up does not happen because the theater mode policy check fails.
-        assertThat(mPolicy.wakeUpFromKey(200, KEYCODE_POWER, true)).isFalse();
+        assertThat(mPolicy.wakeUpFromKey(DEFAULT_DISPLAY, 200, KEYCODE_POWER, true)).isFalse();
         verify(mInputWakeUpDelegate, never()).wakeUpFromKey(anyLong(), anyInt(), anyBoolean());
     }
 
@@ -201,11 +211,13 @@
                 .setInputWakeUpDelegate(mInputWakeUpDelegate);
 
         // Check that the wake up does not happen because the theater mode policy check fails.
-        assertThat(mPolicy.wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true)).isFalse();
+        assertThat(mPolicy.wakeUpFromMotion(
+                mDefaultDisplay.getDisplayId(), 200, SOURCE_TOUCHSCREEN, true)).isFalse();
         verify(mInputWakeUpDelegate, never()).wakeUpFromMotion(anyLong(), anyInt(), anyBoolean());
     }
 
     @Test
+    @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
     public void testTheaterModeChecksNotAppliedWhenScreenIsOn() {
         mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
         setDefaultDisplayState(Display.STATE_ON);
@@ -213,30 +225,69 @@
         setBooleanRes(config_allowTheaterModeWakeFromMotion, false);
         mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
 
-        mPolicy.wakeUpFromMotion(200L, SOURCE_TOUCHSCREEN, true);
+        mPolicy.wakeUpFromMotion(mDefaultDisplay.getDisplayId(), 200L, SOURCE_TOUCHSCREEN, true);
 
         verify(mPowerManager).wakeUp(200L, WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
     }
 
     @Test
+    @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
     public void testWakeUpFromMotion() {
         runPowerManagerUpChecks(
-                () -> mPolicy.wakeUpFromMotion(mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, true),
+                () -> mPolicy.wakeUpFromMotion(mDefaultDisplay.getDisplayId(),
+                        mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, true),
                 config_allowTheaterModeWakeFromMotion,
                 WAKE_REASON_WAKE_MOTION,
                 "android.policy:MOTION");
     }
 
     @Test
+    @EnableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
+    public void testWakeUpFromMotion_perDisplayWakeByTouchEnabled() {
+        setTheaterModeEnabled(false);
+        final int displayId = 555;
+        mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+        boolean displayWokeUp = mPolicy.wakeUpFromMotion(
+                displayId, mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, /* isDown= */ true);
+
+        // Verify that display is woken up
+        assertThat(displayWokeUp).isTrue();
+        verify(mPowerManager).wakeUp(anyLong(), eq(WAKE_REASON_WAKE_MOTION),
+                eq("android.policy:MOTION"), eq(displayId));
+    }
+
+    @Test
+    @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
+    public void testWakeUpFromMotion_perDisplayWakeByTouchDisabled() {
+        setTheaterModeEnabled(false);
+        final int displayId = 555;
+        mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+        boolean displayWokeUp = mPolicy.wakeUpFromMotion(
+                displayId, mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, /* isDown= */ true);
+
+        // Verify that power is woken up and display isn't woken up individually
+        assertThat(displayWokeUp).isTrue();
+        verify(mPowerManager).wakeUp(
+                anyLong(), eq(WAKE_REASON_WAKE_MOTION), eq("android.policy:MOTION"));
+        verify(mPowerManager, never()).wakeUp(anyLong(), eq(WAKE_REASON_WAKE_MOTION),
+                eq("android.policy:MOTION"), eq(displayId));
+    }
+
+    @Test
+    @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
     public void testWakeUpFromKey_nonPowerKey() {
         runPowerManagerUpChecks(
-                () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_HOME, true),
+                () -> mPolicy.wakeUpFromKey(
+                        DEFAULT_DISPLAY, mClock.uptimeMillis(), KEYCODE_HOME, true),
                 config_allowTheaterModeWakeFromKey,
                 WAKE_REASON_WAKE_KEY,
                 "android.policy:KEY");
     }
 
     @Test
+    @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
     public void testWakeUpFromKey_powerKey() {
         // Disable the resource affecting all wake keys because it affects power key as well.
         // That way, power key wake during theater mode will solely be controlled by
@@ -245,7 +296,8 @@
 
         // Test with power key
         runPowerManagerUpChecks(
-                () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, true),
+                () -> mPolicy.wakeUpFromKey(
+                        DEFAULT_DISPLAY, mClock.uptimeMillis(), KEYCODE_POWER, true),
                 config_allowTheaterModeWakeFromPowerKey,
                 WAKE_REASON_POWER_BUTTON,
                 "android.policy:POWER");
@@ -254,13 +306,31 @@
         // even if the power-key specific theater mode config is disabled.
         setBooleanRes(config_allowTheaterModeWakeFromPowerKey, false);
         runPowerManagerUpChecks(
-                () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, false),
+                () -> mPolicy.wakeUpFromKey(
+                        DEFAULT_DISPLAY, mClock.uptimeMillis(), KEYCODE_POWER, false),
                 config_allowTheaterModeWakeFromKey,
                 WAKE_REASON_POWER_BUTTON,
                 "android.policy:POWER");
     }
 
     @Test
+    @EnableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
+    public void testWakeUpFromKey_invalidDisplay_perDisplayWakeByTouchEnabled() {
+        setTheaterModeEnabled(false);
+        final int displayId = Display.INVALID_DISPLAY;
+        mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+        boolean displayWokeUp = mPolicy.wakeUpFromKey(
+                displayId, mClock.uptimeMillis(), KEYCODE_POWER, /* isDown= */ false);
+
+        // Verify that default display is woken up
+        assertThat(displayWokeUp).isTrue();
+        verify(mPowerManager).wakeUp(anyLong(), eq(WAKE_REASON_POWER_BUTTON),
+                eq("android.policy:POWER"), eq(DEFAULT_DISPLAY));
+    }
+
+    @Test
+    @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
     public void testWakeUpFromLid() {
         runPowerManagerUpChecks(
                 () -> mPolicy.wakeUpFromLid(),
@@ -270,6 +340,7 @@
     }
 
     @Test
+    @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
     public void testWakeUpFromWakeGesture() {
         runPowerManagerUpChecks(
                 () -> mPolicy.wakeUpFromWakeGesture(),
@@ -279,6 +350,7 @@
     }
 
     @Test
+    @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
     public void testwakeUpFromCameraCover() {
         runPowerManagerUpChecks(
                 () -> mPolicy.wakeUpFromCameraCover(mClock.uptimeMillis()),
@@ -288,6 +360,7 @@
     }
 
     @Test
+    @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
     public void testWakeUpFromPowerKeyCameraGesture() {
         // Disable the resource affecting all wake keys because it affects power key as well.
         // That way, power key wake during theater mode will solely be controlled by
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 62cbb02..544bfab 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -886,7 +886,7 @@
     /**
     *  @return true if the ImsService to bind to for the slot id specified was set, false otherwise.
     */
-    boolean setBoundImsServiceOverride(int slotIndex, boolean isCarrierService,
+    boolean setBoundImsServiceOverride(int slotIndex, int userId, boolean isCarrierService,
             in int[] featureTypes, in String packageName);
 
     /**
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 332b9b8..9a9a331 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -21,7 +21,7 @@
 import android.graphics.Rect
 import android.graphics.Region
 import android.os.SystemClock
-import android.platform.uiautomator_helpers.DeviceHelpers
+import android.platform.uiautomatorhelpers.DeviceHelpers
 import android.tools.device.apphelpers.IStandardAppHelper
 import android.tools.helpers.SYSTEMUI_PACKAGE
 import android.tools.traces.parsers.WindowManagerStateHelper
@@ -159,7 +159,7 @@
     ) {
         val caption = getCaptionForTheApp(wmHelper, device)
         val maximizeButton = getMaximizeButtonForTheApp(caption)
-        maximizeButton?.longClick()
+        maximizeButton.longClick()
         wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
 
         val buttonResId = if (toLeft) SNAP_LEFT_BUTTON else SNAP_RIGHT_BUTTON
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index f317939c..61400ed 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -757,7 +757,31 @@
                 intArrayOf(KeyEvent.KEYCODE_MINUS),
                 KeyEvent.META_ALT_ON,
                 intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
-            )
+            ),
+            TestData(
+                "META + ALT + '-' -> Magnifier Zoom Out",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_MINUS
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT,
+                intArrayOf(KeyEvent.KEYCODE_MINUS),
+                KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "META + ALT + '=' -> Magnifier Zoom In",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_EQUALS
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN,
+                intArrayOf(KeyEvent.KEYCODE_EQUALS),
+                KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
         )
     }
 
@@ -770,6 +794,7 @@
         com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
         com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
         com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
+        com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
         com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
         com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
     )
@@ -787,6 +812,7 @@
         com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
         com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
         com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
+        com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
         com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
         com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
     )