Merge "Reland "Finish sync for activities under a drawn shared starting window"" into main
diff --git a/Android.bp b/Android.bp
index b139b7e..82b844b 100644
--- a/Android.bp
+++ b/Android.bp
@@ -218,6 +218,7 @@
         "apex_aidl_interface-java",
         "packagemanager_aidl-java",
         "framework-protos",
+        "libtombstone_proto_java",
         "updatable-driver-protos",
         "ota_metadata_proto_java",
         "android.hidl.base-V1.0-java",
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index e7adf20..d03bbd2 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -34,7 +34,6 @@
         ":ipconnectivity-proto-src",
         ":libstats_atom_enum_protos",
         ":libstats_atom_message_protos",
-        ":libtombstone_proto-src",
         "core/proto/**/*.proto",
         "libs/incident/**/*.proto",
     ],
diff --git a/STABILITY_OWNERS b/STABILITY_OWNERS
new file mode 100644
index 0000000..a7ecb4d
--- /dev/null
+++ b/STABILITY_OWNERS
@@ -0,0 +1,2 @@
+gaillard@google.com
+
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 5a32a02..abf8008 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -115,6 +115,7 @@
 import android.os.ShellCallback;
 import android.os.ShellCommand;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.ThreadLocalWorkSource;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -229,6 +230,9 @@
 
     private static final long TEMPORARY_QUOTA_DURATION = INTERVAL_DAY;
 
+    // System property read on some device configurations to initialize time properly.
+    private static final String TIMEOFFSET_PROPERTY = "persist.sys.time.offset";
+
     private final Intent mBackgroundIntent
             = new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
 
@@ -2142,6 +2146,9 @@
             // "GMT" if the ID is unrecognized). The parameter ID is used here rather than
             // newZone.getId(). It will be rejected if it is invalid.
             timeZoneWasChanged = SystemTimeZone.setTimeZoneId(tzId, confidence, logInfo);
+
+            final int gmtOffset = newZone.getOffset(mInjector.getCurrentTimeMillis());
+            SystemProperties.set(TIMEOFFSET_PROPERTY, String.valueOf(gmtOffset));
         }
 
         // Clear the default time zone in the system server process. This forces the next call
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 50c9fd3..ef1fa60 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -912,7 +912,7 @@
 }
 
 // This module can be built with:
-// m out/soong/.intermediates/frameworks/base/api_versions_module_lib/android_common/metalava/api-versions.xml
+// m out/soong/.intermediates/frameworks/base/api/api_versions_module_lib/android_common/metalava/api-versions.xml
 droidstubs {
     name: "api_versions_module_lib",
     srcs: [":android_module_stubs_current_with_test_libs{.jar}"],
diff --git a/core/api/current.txt b/core/api/current.txt
index c7b921c..8d8c6ee 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -102,6 +102,7 @@
     field public static final String FOREGROUND_SERVICE_HEALTH = "android.permission.FOREGROUND_SERVICE_HEALTH";
     field public static final String FOREGROUND_SERVICE_LOCATION = "android.permission.FOREGROUND_SERVICE_LOCATION";
     field public static final String FOREGROUND_SERVICE_MEDIA_PLAYBACK = "android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK";
+    field @FlaggedApi("android.content.pm.introduce_media_processing_type") public static final String FOREGROUND_SERVICE_MEDIA_PROCESSING = "android.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING";
     field public static final String FOREGROUND_SERVICE_MEDIA_PROJECTION = "android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION";
     field public static final String FOREGROUND_SERVICE_MICROPHONE = "android.permission.FOREGROUND_SERVICE_MICROPHONE";
     field public static final String FOREGROUND_SERVICE_PHONE_CALL = "android.permission.FOREGROUND_SERVICE_PHONE_CALL";
@@ -12368,7 +12369,6 @@
 
   public final class ModuleInfo implements android.os.Parcelable {
     method public int describeContents();
-    method @FlaggedApi("android.content.pm.provide_info_of_apk_in_apex") @NonNull public java.util.Collection<java.lang.String> getApkInApexPackageNames();
     method @Nullable public CharSequence getName();
     method @Nullable public String getPackageName();
     method public boolean isHidden();
@@ -12817,7 +12817,7 @@
     method public boolean isPackageSuspended();
     method @CheckResult public abstract boolean isPermissionRevokedByPolicy(@NonNull String, @NonNull String);
     method public abstract boolean isSafeMode();
-    method @FlaggedApi("android.content.pm.get_package_info") @WorkerThread public <T> T parseAndroidManifest(@NonNull String, @NonNull java.util.function.Function<android.content.res.XmlResourceParser,T>) throws java.io.IOException;
+    method @FlaggedApi("android.content.pm.get_package_info") @WorkerThread public <T> T parseAndroidManifest(@NonNull java.io.File, @NonNull java.util.function.Function<android.content.res.XmlResourceParser,T>) throws java.io.IOException;
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryActivityProperty(@NonNull String);
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryApplicationProperty(@NonNull String);
     method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int);
@@ -13287,6 +13287,7 @@
     field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_LOCATION}, anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_LOCATION = 8; // 0x8
     field public static final int FOREGROUND_SERVICE_TYPE_MANIFEST = -1; // 0xffffffff
     field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2; // 0x2
+    field @FlaggedApi("android.content.pm.introduce_media_processing_type") @RequiresPermission(android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING = 8192; // 0x2000
     field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 32; // 0x20
     field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_MICROPHONE}, anyOf={android.Manifest.permission.CAPTURE_AUDIO_OUTPUT, android.Manifest.permission.RECORD_AUDIO}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_MICROPHONE = 128; // 0x80
     field @Deprecated public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0
@@ -26291,6 +26292,7 @@
     field public static final int STATE_FAST_FORWARDING = 4; // 0x4
     field public static final int STATE_NONE = 0; // 0x0
     field public static final int STATE_PAUSED = 2; // 0x2
+    field @FlaggedApi("com.android.media.flags.enable_notifying_activity_manager_with_media_session_status_change") public static final int STATE_PLAYBACK_SUPPRESSED = 12; // 0xc
     field public static final int STATE_PLAYING = 3; // 0x3
     field public static final int STATE_REWINDING = 5; // 0x5
     field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa
@@ -53974,6 +53976,7 @@
     field public static final String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS = "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
     field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
     field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
+    field @FlaggedApi("com.android.window.flags.supports_multi_instance_system_ui") public static final String PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI = "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI";
   }
 
   public static class WindowManager.BadTokenException extends java.lang.RuntimeException {
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 287d2bd..34c44f9 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -131,6 +131,7 @@
 
 import libcore.util.EmptyArray;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.ref.WeakReference;
@@ -4038,11 +4039,11 @@
     }
 
     @Override
-    public <T> T parseAndroidManifest(@NonNull String apkFilePath,
+    public <T> T parseAndroidManifest(@NonNull File apkFile,
             @NonNull Function<XmlResourceParser, T> parserFunction) throws IOException {
-        Objects.requireNonNull(apkFilePath, "apkFilePath cannot be null");
+        Objects.requireNonNull(apkFile, "apkFile cannot be null");
         Objects.requireNonNull(parserFunction, "parserFunction cannot be null");
-        try (XmlResourceParser xmlResourceParser = getAndroidManifestParser(apkFilePath)) {
+        try (XmlResourceParser xmlResourceParser = getAndroidManifestParser(apkFile)) {
             return parserFunction.apply(xmlResourceParser);
         } catch (IOException e) {
             Log.w(TAG, "Failed to get the android manifest parser", e);
@@ -4050,11 +4051,11 @@
         }
     }
 
-    private static XmlResourceParser getAndroidManifestParser(@NonNull String apkFilePath)
+    private static XmlResourceParser getAndroidManifestParser(@NonNull File apkFile)
             throws IOException {
         ApkAssets apkAssets = null;
         try {
-            apkAssets = ApkAssets.loadFromPath(apkFilePath);
+            apkAssets = ApkAssets.loadFromPath(apkFile.getAbsolutePath());
             return apkAssets.openXml(ApkLiteParseUtils.ANDROID_MANIFEST_FILENAME);
         } finally {
             if (apkAssets != null) {
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index ac9c497..d1e517b 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -30,6 +30,7 @@
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
@@ -577,6 +578,26 @@
     );
 
     /**
+     * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING}.
+     *
+     * @hide
+     */
+    public static final @NonNull ForegroundServiceTypePolicyInfo FGS_TYPE_POLICY_MEDIA_PROCESSING =
+            new ForegroundServiceTypePolicyInfo(
+                    FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING,
+                    ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+                    ForegroundServiceTypePolicyInfo.INVALID_CHANGE_ID,
+                    new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
+                            new RegularPermission(
+                                    Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING)
+                    }, true),
+                    null /* anyOfPermissions */,
+                    null /* permissionEnforcementFlag */,
+                    true /* permissionEnforcementFlagDefaultValue */,
+                    false /* foregroundOnlyPermission */
+            );
+
+    /**
      * The policy for the {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE}.
      *
      * @hide
@@ -1331,6 +1352,8 @@
                     FGS_TYPE_POLICY_SYSTEM_EXEMPTED);
             mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
                     FGS_TYPE_POLICY_SHORT_SERVICE);
+            mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING,
+                    FGS_TYPE_POLICY_MEDIA_PROCESSING);
             // TODO (b/271950506): revisit it in the next release.
             // Hide the file management type for now. If anyone uses it, will default to "none".
             mForegroundServiceTypePolicies.put(FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index cdb92ac..843158c 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -71,6 +71,12 @@
      * @see CompanionDeviceManager#disassociate(int)
      */
     private final boolean mRevoked;
+    /**
+     * Indicates that the association is waiting for its corresponding companion app to be installed
+     * before it can be added to CDM. This is likely because it was restored onto the device from a
+     * backup.
+     */
+    private final boolean mPending;
     private final long mTimeApprovedMs;
     /**
      * A long value indicates the last time connected reported by selfManaged devices
@@ -88,7 +94,7 @@
             @Nullable String tag, @Nullable MacAddress macAddress,
             @Nullable CharSequence displayName, @Nullable String deviceProfile,
             @Nullable AssociatedDevice associatedDevice, boolean selfManaged,
-            boolean notifyOnDeviceNearby, boolean revoked, long timeApprovedMs,
+            boolean notifyOnDeviceNearby, boolean revoked, boolean pending, long timeApprovedMs,
             long lastTimeConnectedMs, int systemDataSyncFlags) {
         if (id <= 0) {
             throw new IllegalArgumentException("Association ID should be greater than 0");
@@ -109,6 +115,7 @@
         mSelfManaged = selfManaged;
         mNotifyOnDeviceNearby = notifyOnDeviceNearby;
         mRevoked = revoked;
+        mPending = pending;
         mTimeApprovedMs = timeApprovedMs;
         mLastTimeConnectedMs = lastTimeConnectedMs;
         mSystemDataSyncFlags = systemDataSyncFlags;
@@ -236,6 +243,15 @@
     }
 
     /**
+     * @return true if the association is waiting for its corresponding app to be installed
+     * before it can be added to CDM.
+     * @hide
+     */
+    public boolean isPending() {
+        return mPending;
+    }
+
+    /**
      * @return the last time self reported disconnected for selfManaged only.
      * @hide
      */
@@ -318,6 +334,7 @@
                 + ", mAssociatedDevice=" + mAssociatedDevice
                 + ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby
                 + ", mRevoked=" + mRevoked
+                + ", mPending=" + mPending
                 + ", mTimeApprovedMs=" + new Date(mTimeApprovedMs)
                 + ", mLastTimeConnectedMs=" + (
                     mLastTimeConnectedMs == Long.MAX_VALUE
@@ -336,6 +353,7 @@
                 && mSelfManaged == that.mSelfManaged
                 && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby
                 && mRevoked == that.mRevoked
+                && mPending == that.mPending
                 && mTimeApprovedMs == that.mTimeApprovedMs
                 && mLastTimeConnectedMs == that.mLastTimeConnectedMs
                 && Objects.equals(mPackageName, that.mPackageName)
@@ -351,7 +369,7 @@
     public int hashCode() {
         return Objects.hash(mId, mUserId, mPackageName, mTag, mDeviceMacAddress, mDisplayName,
                 mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked,
-                mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags);
+                mPending, mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags);
     }
 
     @Override
@@ -372,6 +390,7 @@
         dest.writeBoolean(mSelfManaged);
         dest.writeBoolean(mNotifyOnDeviceNearby);
         dest.writeBoolean(mRevoked);
+        dest.writeBoolean(mPending);
         dest.writeLong(mTimeApprovedMs);
         dest.writeLong(mLastTimeConnectedMs);
         dest.writeInt(mSystemDataSyncFlags);
@@ -389,6 +408,7 @@
         mSelfManaged = in.readBoolean();
         mNotifyOnDeviceNearby = in.readBoolean();
         mRevoked = in.readBoolean();
+        mPending = in.readBoolean();
         mTimeApprovedMs = in.readLong();
         mLastTimeConnectedMs = in.readLong();
         mSystemDataSyncFlags = in.readInt();
@@ -427,6 +447,7 @@
         private boolean mSelfManaged;
         private boolean mNotifyOnDeviceNearby;
         private boolean mRevoked;
+        private boolean mPending;
         private long mTimeApprovedMs;
         private long mLastTimeConnectedMs;
         private int mSystemDataSyncFlags;
@@ -453,6 +474,7 @@
             mSelfManaged = info.mSelfManaged;
             mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby;
             mRevoked = info.mRevoked;
+            mPending = info.mPending;
             mTimeApprovedMs = info.mTimeApprovedMs;
             mLastTimeConnectedMs = info.mLastTimeConnectedMs;
             mSystemDataSyncFlags = info.mSystemDataSyncFlags;
@@ -476,6 +498,7 @@
             mSelfManaged = info.mSelfManaged;
             mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby;
             mRevoked = info.mRevoked;
+            mPending = info.mPending;
             mTimeApprovedMs = info.mTimeApprovedMs;
             mLastTimeConnectedMs = info.mLastTimeConnectedMs;
             mSystemDataSyncFlags = info.mSystemDataSyncFlags;
@@ -549,6 +572,14 @@
         }
 
         /** @hide */
+        @NonNull
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setPending(boolean pending) {
+            mPending = pending;
+            return this;
+        }
+
+        /** @hide */
         @TestApi
         @NonNull
         @SuppressLint("MissingGetterMatchingBuilder")
@@ -606,6 +637,7 @@
                     mSelfManaged,
                     mNotifyOnDeviceNearby,
                     mRevoked,
+                    mPending,
                     mTimeApprovedMs,
                     mLastTimeConnectedMs,
                     mSystemDataSyncFlags
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 30871e9..9fe8af5 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -23,7 +23,6 @@
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.app.Activity;
-import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledSince;
@@ -37,7 +36,6 @@
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Printer;
 import android.window.OnBackInvokedCallback;
@@ -1790,8 +1788,7 @@
      * @hide
      */
     public boolean isChangeEnabled(long changeId) {
-        return CompatChanges.isChangeEnabled(changeId, applicationInfo.packageName,
-                UserHandle.getUserHandleForUid(applicationInfo.uid));
+        return applicationInfo.isChangeEnabled(changeId);
     }
 
     /** @hide */
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 869c621..f0a8996 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -26,6 +26,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.compat.CompatChanges;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -2645,6 +2646,17 @@
     }
 
     /**
+     * Checks if a changeId is enabled for the current user
+     * @param changeId The changeId to verify
+     * @return True of the changeId is enabled
+     * @hide
+     */
+    public boolean isChangeEnabled(long changeId) {
+        return CompatChanges.isChangeEnabled(changeId, packageName,
+                UserHandle.getUserHandleForUid(uid));
+    }
+
+    /**
      * @return whether the app has requested exemption from the foreground service restrictions.
      * This does not take any affect for now.
      * @hide
diff --git a/core/java/android/content/pm/ModuleInfo.java b/core/java/android/content/pm/ModuleInfo.java
index a1c8747..c6e93bb 100644
--- a/core/java/android/content/pm/ModuleInfo.java
+++ b/core/java/android/content/pm/ModuleInfo.java
@@ -16,7 +16,6 @@
 
 package android.content.pm;
 
-import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
@@ -122,18 +121,15 @@
         return mApexModuleName;
     }
 
-    /** @hide Sets the list of the package name of APK-in-APEX apps in this module. */
+    /** @hide Set the list of the package names of all APK-in-APEX apps in this module. */
     public ModuleInfo setApkInApexPackageNames(@NonNull Collection<String> apkInApexPackageNames) {
         Objects.requireNonNull(apkInApexPackageNames);
         mApkInApexPackageNames = List.copyOf(apkInApexPackageNames);
         return this;
     }
 
-    /**
-     * Gets the list of the package name of all APK-in-APEX apps in the module.
-     */
+    /** @hide Get the list of the package names of all APK-in-APEX apps in the module. */
     @NonNull
-    @FlaggedApi(android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
     public Collection<String> getApkInApexPackageNames() {
         if (mApkInApexPackageNames == null) {
             return Collections.emptyList();
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 0fb0993..8e5e825 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -11534,14 +11534,14 @@
     }
 
     /**
-     * Retrieve AndroidManifest.xml information for the given application apk path.
+     * Retrieve AndroidManifest.xml information for the given application apk file.
      *
      * <p>Example:
      *
      * <pre><code>
      * Bundle result;
      * try {
-     *     result = getContext().getPackageManager().parseAndroidManifest(apkFilePath,
+     *     result = getContext().getPackageManager().parseAndroidManifest(apkFile,
      *             xmlResourceParser -> {
      *                 Bundle bundle = new Bundle();
      *                 // Search the start tag
@@ -11570,9 +11570,10 @@
      *
      * Note: When the parserFunction is invoked, the client can read the AndroidManifest.xml
      * information by the XmlResourceParser object. After leaving the parserFunction, the
-     * XmlResourceParser object will be closed.
+     * XmlResourceParser object will be closed. The caller should also handle the exception for
+     * calling this method.
      *
-     * @param apkFilePath The path of an application apk file.
+     * @param apkFile The file of an application apk.
      * @param parserFunction The parserFunction will be invoked with the XmlResourceParser object
      *        after getting the AndroidManifest.xml of an application package.
      *
@@ -11583,7 +11584,7 @@
      */
     @FlaggedApi(android.content.pm.Flags.FLAG_GET_PACKAGE_INFO)
     @WorkerThread
-    public <T> T parseAndroidManifest(@NonNull String apkFilePath,
+    public <T> T parseAndroidManifest(@NonNull File apkFile,
             @NonNull Function<XmlResourceParser, T> parserFunction) throws IOException {
         throw new UnsupportedOperationException(
                 "parseAndroidManifest not implemented in subclass");
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 4d704c3..ae46c027 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -17,6 +17,7 @@
 package android.content.pm;
 
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.RequiresPermission;
 import android.os.Parcel;
@@ -471,6 +472,17 @@
     public static final int FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT = 1 << 12;
 
     /**
+     * Constant corresponding to {@code mediaProcessing} in
+     * the {@link android.R.attr#foregroundServiceType} attribute.
+     * Media processing use cases such as video or photo editing and processing.
+     */
+    @RequiresPermission(
+            value = Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING
+    )
+    @FlaggedApi(Flags.FLAG_INTRODUCE_MEDIA_PROCESSING_TYPE)
+    public static final int FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING = 1 << 13;
+
+    /**
      * Constant corresponding to {@code specialUse} in
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * Use cases that can't be categorized into any other foreground service types, but also
@@ -554,6 +566,7 @@
             FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED,
             FOREGROUND_SERVICE_TYPE_SHORT_SERVICE,
             FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT,
+            FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING,
             FOREGROUND_SERVICE_TYPE_SPECIAL_USE,
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -640,6 +653,8 @@
                 return "shortService";
             case FOREGROUND_SERVICE_TYPE_FILE_MANAGEMENT:
                 return "fileManagement";
+            case FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING:
+                return "mediaProcessing";
             case FOREGROUND_SERVICE_TYPE_SPECIAL_USE:
                 return "specialUse";
             default:
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 94bec35..0b60977 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -131,3 +131,10 @@
     bug: "310801107"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "introduce_media_processing_type"
+    namespace: "backstage_power"
+    description: "Add a new FGS type for media processing use cases."
+    bug: "317788011"
+}
diff --git a/core/java/android/content/res/Element.java b/core/java/android/content/res/Element.java
index e511469..89f4985 100644
--- a/core/java/android/content/res/Element.java
+++ b/core/java/android/content/res/Element.java
@@ -26,6 +26,8 @@
 
 import com.android.internal.R;
 
+import java.util.Set;
+
 /**
  * Defines the string attribute length and child tag count restrictions for a xml element.
  *
@@ -37,7 +39,11 @@
     private static final int MAX_ATTR_LEN_URL_COMPONENT = 256;
     private static final int MAX_ATTR_LEN_PERMISSION_GROUP = 256;
     private static final int MAX_ATTR_LEN_PACKAGE = 256;
-    private static final int MAX_ATTR_LEN_MIMETYPE = 512;
+    /**
+     * The mime type max length restriction here should match the restriction that is also
+     * placed in {@link android.content.pm.PackageManager#setMimeGroup(String, Set)}
+     */
+    private static final int MAX_ATTR_LEN_MIMETYPE = 255;
     private static final int MAX_ATTR_LEN_NAME = 1024;
     private static final int MAX_ATTR_LEN_PATH = 4000;
     private static final int MAX_ATTR_LEN_VALUE = 32_768;
@@ -103,6 +109,7 @@
     protected static final String TAG_ATTR_HOST = "host";
     protected static final String TAG_ATTR_MANAGE_SPACE_ACTIVITY = "manageSpaceActivity";
     protected static final String TAG_ATTR_MIMETYPE = "mimeType";
+    protected static final String TAG_ATTR_MIMEGROUP = "mimeGroup";
     protected static final String TAG_ATTR_NAME = "name";
     protected static final String TAG_ATTR_PACKAGE = "package";
     protected static final String TAG_ATTR_PATH = "path";
@@ -367,6 +374,7 @@
             case TAG_ATTR_BACKUP_AGENT:
             case TAG_ATTR_CATEGORY:
             case TAG_ATTR_MANAGE_SPACE_ACTIVITY:
+            case TAG_ATTR_MIMEGROUP:
             case TAG_ATTR_NAME:
             case TAG_ATTR_PARENT_ACTIVITY_NAME:
             case TAG_ATTR_PERMISSION:
@@ -520,6 +528,8 @@
                 return MAX_ATTR_LEN_URL_COMPONENT;
             case R.styleable.AndroidManifestData_mimeType:
                 return MAX_ATTR_LEN_MIMETYPE;
+            case R.styleable.AndroidManifestData_mimeGroup:
+                return MAX_ATTR_LEN_NAME;
             case R.styleable.AndroidManifestData_path:
             case R.styleable.AndroidManifestData_pathPattern:
             case R.styleable.AndroidManifestData_pathPrefix:
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 3fcb3da..47ee76e 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -26,6 +26,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
@@ -755,7 +756,10 @@
         @Override
         public void onPendingIntent(PendingIntent pendingIntent) {
             try {
-                mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+                mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0,
+                        ActivityOptions.makeBasic()
+                            .setPendingIntentBackgroundActivityStartMode(
+                                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle());
             } catch (IntentSender.SendIntentException e) {
                 Log.e(
                         TAG,
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index a9b7257..5871717 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1315,9 +1315,7 @@
         if (IS_ENG) return true;
 
         if (IS_TREBLE_ENABLED) {
-            // If we can run this code, the device should already pass AVB.
-            // So, we don't need to check AVB here.
-            int result = VintfObject.verifyWithoutAvb();
+            int result = VintfObject.verifyBuildAtBoot();
 
             if (result != 0) {
                 Slog.e(TAG, "Vendor interface is incompatible, error="
diff --git a/core/java/android/os/ISystemConfig.aidl b/core/java/android/os/ISystemConfig.aidl
index 61b24aa..b7649ba 100644
--- a/core/java/android/os/ISystemConfig.aidl
+++ b/core/java/android/os/ISystemConfig.aidl
@@ -52,4 +52,9 @@
      * @see SystemConfigManager#getDefaultVrComponents
      */
     List<ComponentName> getDefaultVrComponents();
+
+    /**
+     * @see SystemConfigManager#getPreventUserDisablePackages
+     */
+    List<String> getPreventUserDisablePackages();
 }
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index d3f2c7a..eb5b511 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -94,4 +94,8 @@
 per-file Temperature.java = file:/THERMAL_OWNERS
 
 # SecurityStateManager
-per-file *SecurityStateManager* = file:/SECURITY_STATE_OWNERS
\ No newline at end of file
+per-file *SecurityStateManager* = file:/SECURITY_STATE_OWNERS
+
+# SystemConfig
+per-file ISystemConfig.aidl = file:/PACKAGE_MANAGER_OWNERS
+per-file SystemConfigManager.java = file:/PACKAGE_MANAGER_OWNERS
diff --git a/core/java/android/os/SystemConfigManager.java b/core/java/android/os/SystemConfigManager.java
index 77843d9..21ffbf1 100644
--- a/core/java/android/os/SystemConfigManager.java
+++ b/core/java/android/os/SystemConfigManager.java
@@ -161,4 +161,18 @@
         }
         return Collections.emptyList();
     }
+
+    /**
+     * Return the packages that are prevented from being disabled, where if
+     * disabled it would result in a non-functioning system or similar.
+     * @hide
+     */
+    @NonNull
+    public List<String> getPreventUserDisablePackages() {
+        try {
+            return mInterface.getPreventUserDisablePackages();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/os/VintfObject.java b/core/java/android/os/VintfObject.java
index 1f11197..4fc5131 100644
--- a/core/java/android/os/VintfObject.java
+++ b/core/java/android/os/VintfObject.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.TestApi;
-import android.util.Slog;
 
 import java.util.Map;
 
@@ -44,44 +43,8 @@
     public static native String[] report();
 
     /**
-     * Verify that the given metadata for an OTA package is compatible with
-     * this device.
-     *
-     * @param packageInfo a list of serialized form of HalManifest's /
-     * CompatibilityMatri'ces (XML).
-     * @return = 0 if success (compatible)
-     *         &gt; 0 if incompatible
-     *         &lt; 0 if any error (mount partition fails, illformed XML, etc.)
-     *
-     * @deprecated Checking compatibility against an OTA package is no longer
-     * supported because the format of VINTF metadata in the OTA package may not
-     * be recognized by the current system.
-     *
-     * <p>
-     * <ul>
-     * <li>This function always returns 0 for non-empty {@code packageInfo}.
-     * </li>
-     * <li>This function returns the result of {@link #verifyWithoutAvb} for
-     * null or empty {@code packageInfo}.</li>
-     * </ul>
-     *
-     * @hide
-     */
-    @Deprecated
-    public static int verify(String[] packageInfo) {
-        if (packageInfo != null && packageInfo.length > 0) {
-            Slog.w(LOG_TAG, "VintfObject.verify() with non-empty packageInfo is deprecated. "
-                    + "Skipping compatibility checks for update package.");
-            return 0;
-        }
-        Slog.w(LOG_TAG, "VintfObject.verify() is deprecated. Call verifyWithoutAvb() instead.");
-        return verifyWithoutAvb();
-    }
-
-    /**
-     * Verify Vintf compatibility on the device without checking AVB
-     * (Android Verified Boot). It is useful to verify a running system
-     * image where AVB check is irrelevant.
+     * Verify Vintf compatibility on the device at boot time. Certain checks
+     * like kernel checks, AVB checks are disabled.
      *
      * @return = 0 if success (compatible)
      *         > 0 if incompatible
@@ -89,7 +52,7 @@
      *
      * @hide
      */
-    public static native int verifyWithoutAvb();
+    public static native int verifyBuildAtBoot();
 
     /**
      * @return a list of HAL names and versions that is supported by this
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 84fddcb..144e64f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -108,10 +108,10 @@
 import java.lang.annotation.Target;
 import java.lang.reflect.Field;
 import java.net.URISyntaxException;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
@@ -3585,10 +3585,12 @@
                     || applicationInfo.isSignedWithPlatformKey();
         }
 
-        public ArrayMap<String, String> getStringsForPrefix(ContentResolver cr, String prefix,
-                List<String> names) {
+        private ArrayMap<String, String> getStringsForPrefixStripPrefix(
+                ContentResolver cr, String prefix, String[] names) {
             String namespace = prefix.substring(0, prefix.length() - 1);
             ArrayMap<String, String> keyValues = new ArrayMap<>();
+            int substringLength = prefix.length();
+
             int currentGeneration = -1;
             boolean needsGenerationTracker = false;
 
@@ -3613,10 +3615,13 @@
                             if (DEBUG) {
                                 Log.i(TAG, "Cache hit for prefix:" + prefix);
                             }
-                            if (!names.isEmpty()) {
+                            if (names.length > 0) {
                                 for (String name : names) {
-                                    if (mValues.containsKey(name)) {
-                                        keyValues.put(name, mValues.get(name));
+                                    String value = mValues.get(name);
+                                    if (value != null) {
+                                        keyValues.put(
+                                                name.substring(substringLength),
+                                                value);
                                     }
                                 }
                             } else {
@@ -3625,7 +3630,10 @@
                                     // Explicitly exclude the prefix as it is only there to
                                     // signal that the prefix has been cached.
                                     if (key.startsWith(prefix) && !key.equals(prefix)) {
-                                        keyValues.put(key, mValues.get(key));
+                                        String value = mValues.valueAt(i);
+                                        keyValues.put(
+                                                key.substring(substringLength),
+                                                value);
                                     }
                                 }
                             }
@@ -3685,14 +3693,22 @@
                 Map<String, String> flagsToValues =
                         (HashMap) b.getSerializable(Settings.NameValueTable.VALUE, java.util.HashMap.class);
                 // Only the flags requested by the caller
-                if (!names.isEmpty()) {
-                    for (Map.Entry<String, String> flag : flagsToValues.entrySet()) {
-                        if (names.contains(flag.getKey())) {
-                            keyValues.put(flag.getKey(), flag.getValue());
+                if (names.length > 0) {
+                    for (String name : names) {
+                        String value = flagsToValues.get(name);
+                        if (value != null) {
+                            keyValues.put(
+                                    name.substring(substringLength),
+                                    value);
                         }
                     }
                 } else {
-                    keyValues.putAll(flagsToValues);
+                    keyValues.ensureCapacity(keyValues.size() + flagsToValues.size());
+                    for (Map.Entry<String, String> flag : flagsToValues.entrySet()) {
+                        keyValues.put(
+                                flag.getKey().substring(substringLength),
+                                flag.getValue());
+                    }
                 }
 
                 synchronized (NameValueCache.this) {
@@ -12172,6 +12188,7 @@
             CLONE_TO_MANAGED_PROFILE.add(SHOW_IME_WITH_HARD_KEYBOARD);
             CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_BOUNCE_KEYS);
             CLONE_TO_MANAGED_PROFILE.add(NOTIFICATION_BUBBLES);
+            CLONE_TO_MANAGED_PROFILE.add(NOTIFICATION_HISTORY_ENABLED);
         }
 
         /** @hide */
@@ -19674,6 +19691,15 @@
             @Readable
             public static final String WRIST_DETECTION_AUTO_LOCKING_ENABLED =
                     "wear_wrist_detection_auto_locking_enabled";
+
+            /**
+             * Whether consistent notification blocking experience is enabled.
+             *
+             * @hide
+             */
+            @Readable
+            public static final String CONSISTENT_NOTIFICATION_BLOCKING_ENABLED =
+                    "consistent_notification_blocking_enabled";
         }
     }
 
@@ -19834,21 +19860,15 @@
         @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
         public static Map<String, String> getStrings(@NonNull ContentResolver resolver,
                 @NonNull String namespace, @NonNull List<String> names) {
-            List<String> compositeNames = new ArrayList<>(names.size());
-            for (String name : names) {
-                compositeNames.add(createCompositeName(namespace, name));
+            String[] compositeNames = new String[names.size()];
+            for (int i = 0, size = names.size(); i < size; ++i) {
+                compositeNames[i] = createCompositeName(namespace, names.get(i));
             }
 
             String prefix = createPrefix(namespace);
-            ArrayMap<String, String> rawKeyValues = sNameValueCache.getStringsForPrefix(
+
+            ArrayMap<String, String> keyValues = sNameValueCache.getStringsForPrefixStripPrefix(
                     resolver, prefix, compositeNames);
-            int size = rawKeyValues.size();
-            int substringLength = prefix.length();
-            ArrayMap<String, String> keyValues = new ArrayMap<>(size);
-            for (int i = 0; i < size; ++i) {
-                keyValues.put(rawKeyValues.keyAt(i).substring(substringLength),
-                        rawKeyValues.valueAt(i));
-            }
             return keyValues;
         }
 
@@ -20174,12 +20194,13 @@
         private static String createCompositeName(@NonNull String namespace, @NonNull String name) {
             Preconditions.checkNotNull(namespace);
             Preconditions.checkNotNull(name);
-            return createPrefix(namespace) + name;
+            var sb = new StringBuilder(namespace.length() + 1 + name.length());
+            return sb.append(namespace).append('/').append(name).toString();
         }
 
         private static String createPrefix(@NonNull String namespace) {
             Preconditions.checkNotNull(namespace);
-            return namespace + "/";
+            return namespace + '/';
         }
 
         private static Uri createNamespaceUri(@NonNull String namespace) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index f61ed51..b4ac9a2 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1494,6 +1494,30 @@
             "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
 
     /**
+     * Activity or Application level {@link android.content.pm.PackageManager.Property
+     * PackageManager.Property} for an app to declare that System UI should be shown for this
+     * app/component to allow it to be launched as multiple instances.  This property only affects
+     * SystemUI behavior and does _not_ affect whether a component can actually be launched into
+     * multiple instances, which is determined by the Activity's {@code launchMode} or the launching
+     * Intent's flags.  If the property is set on the Application, then all components within that
+     * application will use that value unless specified per component.
+     *
+     * The value must be a boolean string.
+     *
+     * <p><b>Syntax:</b>
+     * <pre>
+     * &lt;activity&gt;
+     *   &lt;property
+     *     android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
+     *     android:value="true|false"/&gt;
+     * &lt;/activity&gt;
+     * </pre>
+     */
+    @FlaggedApi(Flags.FLAG_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI)
+    public static final String PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI =
+            "android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI";
+
+    /**
      * Request for app's keyboard shortcuts to be retrieved asynchronously.
      *
      * @param receiver The callback to be triggered when the result is ready.
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index feccc6b..3bc02a6 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -354,7 +354,11 @@
      * @hide
      */
     public static void ensureDefaultInstanceForDefaultDisplayIfNecessary() {
-        forContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper());
+        // Skip this call if we are in system_server, as the system code should not use this
+        // deprecated instance.
+        if (!ActivityThread.isSystem()) {
+            forContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper());
+        }
     }
 
     private static final Object sLock = new Object();
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 3366a7e..f2bce9c 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -82,4 +82,12 @@
     description: "Enable record activity snapshot by default"
     bug: "259497289"
     is_fixed_read_only: true
+}
+
+flag {
+    name: "supports_multi_instance_system_ui"
+    namespace: "multitasking"
+    description: "Feature flag to enable a multi-instance system ui component property."
+    bug: "262864589"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 1bd0982..eeea17b 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -68,10 +68,10 @@
         // TODO b/291899544: for released flags, use resource config values
         /** Value used by polite notif. feature */
         public static final Flag NOTIF_COOLDOWN_T1 = devFlag(
-                "persist.debug.sysui.notification.notif_cooldown_t1", 5000);
+                "persist.debug.sysui.notification.notif_cooldown_t1", 60000);
         /** Value used by polite notif. feature */
         public static final Flag NOTIF_COOLDOWN_T2 = devFlag(
-                "persist.debug.sysui.notification.notif_cooldown_t2", 3000);
+                "persist.debug.sysui.notification.notif_cooldown_t2", 5000);
         /** Value used by polite notif. feature */
         public static final Flag NOTIF_VOLUME1 = devFlag(
                 "persist.debug.sysui.notification.notif_volume1", 30);
@@ -80,12 +80,6 @@
         /** Value used by polite notif. feature. -1 to ignore the counter */
         public static final Flag NOTIF_COOLDOWN_COUNTER_RESET = devFlag(
                 "persist.debug.sysui.notification.notif_cooldown_counter_reset", 10);
-        /**
-         * Value used by polite notif. feature: cooldown behavior/strategy. Valid values: rule1,
-         * rule2
-         */
-        public static final Flag NOTIF_COOLDOWN_RULE = devFlag(
-                "persist.debug.sysui.notification.notif_cooldown_rule", "rule1");
 
         /** b/303716154: For debugging only: use short bitmap duration. */
         public static final Flag DEBUG_SHORT_BITMAP_DURATION = devFlag(
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 54fdcc6..4e3b64c 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -47,6 +47,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Resources.Theme;
@@ -389,10 +390,7 @@
         mProxyOnBackInvokedDispatcher = new ProxyOnBackInvokedDispatcher(context);
         mAllowFloatingWindowsFillScreen = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_allowFloatingWindowsFillScreen);
-        mEdgeToEdgeEnforced =
-                context.getApplicationInfo().targetSdkVersion >= ENFORCE_EDGE_TO_EDGE_SDK_VERSION
-                        || (CompatChanges.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)
-                                && Flags.enforceEdgeToEdge());
+        mEdgeToEdgeEnforced = isEdgeToEdgeEnforced(context.getApplicationInfo(), true /* local */);
         if (mEdgeToEdgeEnforced) {
             getAttributes().privateFlags |= PRIVATE_FLAG_EDGE_TO_EDGE_ENFORCED;
             mDecorFitsSystemWindows = false;
@@ -433,6 +431,22 @@
         mActivityConfigCallback = activityConfigCallback;
     }
 
+    /**
+     * Returns whether the given application is enforced to go edge-to-edge.
+     *
+     * @param info The application to query.
+     * @param local Whether this is called from the process of the given application.
+     * @return {@code true} if edge-to-edge is enforced. Otherwise, {@code false}.
+     */
+    public static boolean isEdgeToEdgeEnforced(ApplicationInfo info, boolean local) {
+        return info.targetSdkVersion >= ENFORCE_EDGE_TO_EDGE_SDK_VERSION
+                || (Flags.enforceEdgeToEdge() && (local
+                        // Calling this doesn't require a permission.
+                        ? CompatChanges.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)
+                        // Calling this requires permissions.
+                        : info.isChangeEnabled(ENFORCE_EDGE_TO_EDGE)));
+    }
+
     @Override
     public final void setContainer(Window container) {
         super.setContainer(container);
diff --git a/core/java/com/android/server/OWNERS b/core/java/com/android/server/OWNERS
deleted file mode 100644
index 1c2d19d..0000000
--- a/core/java/com/android/server/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-per-file SystemConfig.java = file:/PACKAGE_MANAGER_OWNERS
diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp
index 1baea2a..b651711 100644
--- a/core/jni/android_os_VintfObject.cpp
+++ b/core/jni/android_os_VintfObject.cpp
@@ -46,6 +46,7 @@
 using vintf::Version;
 using vintf::VintfObject;
 using vintf::Vndk;
+using vintf::CheckFlags::ENABLE_ALL_CHECKS;
 
 template<typename V>
 static inline jobjectArray toJavaStringArray(JNIEnv* env, const V& v) {
@@ -93,12 +94,13 @@
     return toJavaStringArray(env, cStrings);
 }
 
-static jint android_os_VintfObject_verifyWithoutAvb(JNIEnv* env, jclass) {
+static jint android_os_VintfObject_verifyBuildAtBoot(JNIEnv* env, jclass) {
     std::string error;
-    int32_t status = VintfObject::GetInstance()->checkCompatibility(&error,
-            ::android::vintf::CheckFlags::DISABLE_AVB_CHECK);
+    int32_t status =
+            VintfObject::GetInstance()
+                    ->checkCompatibility(&error, ENABLE_ALL_CHECKS.disableAvb().disableKernel());
     if (status)
-        LOG(WARNING) << "VintfObject.verifyWithoutAvb() returns " << status << ": " << error;
+        LOG(WARNING) << "VintfObject.verifyBuildAtBoot() returns " << status << ": " << error;
     return status;
 }
 
@@ -170,7 +172,7 @@
 
 static const JNINativeMethod gVintfObjectMethods[] = {
         {"report", "()[Ljava/lang/String;", (void*)android_os_VintfObject_report},
-        {"verifyWithoutAvb", "()I", (void*)android_os_VintfObject_verifyWithoutAvb},
+        {"verifyBuildAtBoot", "()I", (void*)android_os_VintfObject_verifyBuildAtBoot},
         {"getHalNamesAndVersions", "()[Ljava/lang/String;",
          (void*)android_os_VintfObject_getHalNamesAndVersions},
         {"getSepolicyVersion", "()Ljava/lang/String;",
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1eeffb9..ef6caef 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7147,6 +7147,16 @@
         android:label="@string/permlab_foregroundServiceFileManagement"
         android:protectionLevel="normal|instant" />
 
+    <!-- @FlaggedApi("android.content.pm.introduce_media_processing_type")
+         Allows a regular application to use {@link android.app.Service#startForeground
+         Service.startForeground} with the type "mediaProcessing".
+         <p>Protection level: normal|instant
+    -->
+    <permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING"
+                android:description="@string/permdesc_foregroundServiceMediaProcessing"
+                android:label="@string/permlab_foregroundServiceMediaProcessing"
+                android:protectionLevel="normal|instant" />
+
     <!-- Allows a regular application to use {@link android.app.Service#startForeground
          Service.startForeground} with the type "specialUse".
          <p>Protection level: normal|appop|instant
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index a23201e..8fae6db 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1744,6 +1744,12 @@
             TODO: b/258855262 mark this field as {@code hide} once this bug is fixed.
             <flag name="fileManagement" value="0x1000" />
         -->
+        <!-- Media processing use cases such as video or photo editing and processing.
+            <p>Requires the app to hold the permission
+            {@link android.Manifest.permission#FOREGROUND_SERVICE_MEDIA_PROCESSING} in order to use
+            this type.
+        -->
+        <flag name="mediaProcessing" value="0x2000" />
         <!-- Use cases that can't be categorized into any other foreground service types, but also
             can't use @link android.app.job.JobInfo.Builder} APIs.
             See {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SPECIAL_USE} for the
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 8d80af4..5346454 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -212,6 +212,36 @@
     <bool name="config_send_satellite_datagram_to_modem_in_demo_mode">false</bool>
     <java-symbol type="bool" name="config_send_satellite_datagram_to_modem_in_demo_mode" />
 
+    <!-- List of country codes where oem-enabled satellite services are either allowed or disallowed
+         by the device. Each country code is a lowercase 2 character ISO-3166-1 alpha-2.
+         -->
+    <string-array name="config_oem_enabled_satellite_country_codes">
+    </string-array>
+    <java-symbol type="array" name="config_oem_enabled_satellite_country_codes" />
+
+    <!-- The file storing S2-cell-based satellite access restriction of the countries defined by
+         config_oem_enabled_satellite_countries. -->
+    <string name="config_oem_enabled_satellite_s2cell_file"></string>
+    <java-symbol type="string" name="config_oem_enabled_satellite_s2cell_file" />
+
+    <!-- Whether to treat the countries defined by the config_oem_enabled_satellite_countries
+         as satellite-allowed areas. The default true value means the countries defined by
+         config_oem_enabled_satellite_countries will be treated as satellite-allowed areas.
+         -->
+    <bool name="config_oem_enabled_satellite_access_allow">true</bool>
+    <java-symbol type="bool" name="config_oem_enabled_satellite_access_allow" />
+
+    <!-- The time duration in seconds which is used to decide whether the Location returned from
+         LocationManager#getLastKnownLocation is fresh.
+
+         The Location is considered fresh if the duration from the Location's elapsed real time to
+         the current elapsed real time is less than this config. If the Location is considered
+         fresh, it will be used as the current location by Telephony to decide whether satellite
+         services should be allowed.
+         -->
+    <integer name="config_oem_enabled_satellite_location_fresh_duration">600</integer>
+    <java-symbol type="integer" name="config_oem_enabled_satellite_location_fresh_duration" />
+
     <!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks
          will not perform handover if the target transport is out of service, or VoPS not
          supported. The network will be torn down on the source transport, and will be
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d2fb9e1..542e9d6 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1248,6 +1248,11 @@
     <string name="permdesc_foregroundServiceFileManagement">Allows the app to make use of foreground services with the type \"fileManagement\"</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_foregroundServiceMediaProcessing">run foreground service with the type \"mediaProcessing\"</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_foregroundServiceMediaProcessing">Allows the app to make use of foreground services with the type \"mediaProcessing\"</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_foregroundServiceSpecialUse">run foreground service with the type \"specialUse\"</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_foregroundServiceSpecialUse">Allows the app to make use of foreground services with the type \"specialUse\"</string>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index b9efe65..a1ea2b8 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -134,6 +134,7 @@
         <permission name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
         <permission name="android.permission.DUMP"/>
         <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+        <permission name="android.permission.LOCATION_BYPASS"/>
         <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
         <permission name="android.permission.MANAGE_USERS"/>
         <permission name="android.permission.MANAGE_SUBSCRIPTION_PLANS" />
@@ -149,6 +150,7 @@
         <permission name="android.permission.REGISTER_CALL_PROVIDER"/>
         <permission name="android.permission.REGISTER_SIM_SUBSCRIPTION"/>
         <permission name="android.permission.REGISTER_STATS_PULL_ATOM"/>
+        <permission name="android.permission.SATELLITE_COMMUNICATION"/>
         <permission name="android.permission.SEND_RESPOND_VIA_MESSAGE"/>
         <permission name="android.permission.SHUTDOWN"/>
         <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
diff --git a/data/keyboards/Vendor_0957_Product_0031.kl b/data/keyboards/Vendor_0957_Product_0031.kl
new file mode 100644
index 0000000..b47ee58
--- /dev/null
+++ b/data/keyboards/Vendor_0957_Product_0031.kl
@@ -0,0 +1,82 @@
+# Copyright 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.
+#
+# Key Layout file for Google Reference RCU Remote with customizable button.
+#
+
+key 116   TV_POWER      WAKE
+key 217   ASSIST        WAKE
+key 423   MACRO_1       WAKE
+
+key 103   DPAD_UP
+key 108   DPAD_DOWN
+key 105   DPAD_LEFT
+key 106   DPAD_RIGHT
+key 353   DPAD_CENTER
+
+key 158   BACK
+key 172   HOME          WAKE
+
+key 113   VOLUME_MUTE
+key 114   VOLUME_DOWN
+key 115   VOLUME_UP
+
+key 2     1
+key 3     2
+key 4     3
+key 5     4
+key 6     5
+key 7     6
+key 8     7
+key 9     8
+key 10    9
+key 11    0
+
+# custom keys
+key usage 0x000c01BB    TV_INPUT
+
+key usage 0x000c0185    TV_TELETEXT
+key usage 0x000c0061    CAPTIONS
+
+key usage 0x000c01BD    INFO
+key usage 0x000c0037    PERIOD
+
+key usage 0x000c0069    PROG_RED
+key usage 0x000c006A    PROG_GREEN
+key usage 0x000c006C    PROG_YELLOW
+key usage 0x000c006B    PROG_BLUE
+key usage 0x000c00B4    MEDIA_SKIP_BACKWARD
+key usage 0x000c00CD    MEDIA_PLAY_PAUSE
+key usage 0x000c00B2    MEDIA_RECORD
+key usage 0x000c00B3    MEDIA_SKIP_FORWARD
+
+key usage 0x000c022A    BOOKMARK
+key usage 0x000c01A2    ALL_APPS
+key usage 0x000c019C    PROFILE_SWITCH
+
+key usage 0x000c0096    SETTINGS
+key usage 0x000c009F    NOTIFICATION
+
+key usage 0x000c008D    GUIDE
+key usage 0x000c0089    TV
+
+key usage 0x000c0187    FEATURED_APP_1    WAKE #FreeTv
+
+key usage 0x000c009C    CHANNEL_UP
+key usage 0x000c009D    CHANNEL_DOWN
+
+key usage 0x000c0077    BUTTON_3     WAKE #YouTube
+key usage 0x000c0078    BUTTON_4     WAKE #Netflix
+key usage 0x000c0079    BUTTON_6     WAKE
+key usage 0x000c007A    BUTTON_7     WAKE
\ No newline at end of file
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 592f9a5..80afb16d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -382,9 +382,13 @@
         if (splitAttributes == null) {
             return TaskFragmentAnimationParams.DEFAULT;
         }
-        return new TaskFragmentAnimationParams.Builder()
-                // TODO(b/263047900): Update extensions API.
-                // .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
-                .build();
+        final AnimationBackground animationBackground = splitAttributes.getAnimationBackground();
+        if (animationBackground instanceof AnimationBackground.ColorBackground colorBackground) {
+            return new TaskFragmentAnimationParams.Builder()
+                    .setAnimationBackgroundColor(colorBackground.getColor())
+                    .build();
+        } else {
+            return TaskFragmentAnimationParams.DEFAULT;
+        }
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 6f356fa..8b7fd10 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -893,8 +893,7 @@
         return new SplitAttributes.Builder()
                 .setSplitType(splitTypeToUpdate)
                 .setLayoutDirection(splitAttributes.getLayoutDirection())
-                // TODO(b/263047900): Update extensions API.
-                // .setAnimationBackgroundColor(splitAttributes.getAnimationBackgroundColor())
+                .setAnimationBackground(splitAttributes.getAnimationBackground())
                 .build();
     }
 
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index 60beb0b..f471af0 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -25,6 +25,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
+import androidx.window.extensions.embedding.AnimationBackground;
 import androidx.window.extensions.embedding.SplitAttributes;
 
 import org.junit.Before;
@@ -70,7 +71,7 @@
                 .isEqualTo(SplitAttributes.LayoutDirection.LOCALE);
         assertThat(splitAttributes.getSplitType())
                 .isEqualTo(new SplitAttributes.SplitType.RatioSplitType(0.5f));
-        // TODO(b/263047900): Update extensions API.
-        // assertThat(splitAttributes.getAnimationBackgroundColor()).isEqualTo(0);
+        assertThat(splitAttributes.getAnimationBackground())
+                .isEqualTo(AnimationBackground.ANIMATION_BACKGROUND_DEFAULT);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index ac75c73..06210ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -20,6 +20,7 @@
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
+import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 
 import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationSpec.createShowSnapshotForClosingAnimation;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
@@ -330,6 +331,9 @@
             if (!animation.hasExtension()) {
                 continue;
             }
+            if (adapter.mChange.hasFlags(FLAG_TRANSLUCENT)) {
+                continue;
+            }
             final TransitionInfo.Change change = adapter.mChange;
             if (TransitionUtil.isOpeningType(adapter.mChange.getMode())) {
                 // Need to screenshot after startTransaction is applied otherwise activity
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index 0693543..662f325 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.common.split;
 
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
@@ -24,19 +24,27 @@
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.PendingIntent;
-import android.content.Context;
+import android.content.ComponentName;
 import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.wm.shell.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
 
+import java.util.Arrays;
+import java.util.List;
+
 /** Helper utility class for split screen components to use. */
 public class SplitScreenUtils {
     /** Reverse the split position. */
@@ -135,4 +143,28 @@
             return isLandscape;
         }
     }
+
+    /** Returns the component from a PendingIntent */
+    @Nullable
+    public static ComponentName getComponent(@Nullable PendingIntent pendingIntent) {
+        if (pendingIntent == null || pendingIntent.getIntent() == null) {
+            return null;
+        }
+        return pendingIntent.getIntent().getComponent();
+    }
+
+    /** Returns the component from a shortcut */
+    @Nullable
+    public static ComponentName getShortcutComponent(@NonNull String packageName, String shortcutId,
+            @NonNull UserHandle user, @NonNull LauncherApps launcherApps) {
+        LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
+        query.setPackage(packageName);
+        query.setShortcutIds(Arrays.asList(shortcutId));
+        query.setQueryFlags(FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED);
+        List<ShortcutInfo> shortcuts = launcherApps.getShortcuts(query, user);
+        ShortcutInfo info = shortcuts != null && shortcuts.size() > 0
+                ? shortcuts.get(0)
+                : null;
+        return info != null ? info.getActivity() : null;
+    }
 }
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 0ef047f..36f06e8 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
@@ -210,7 +210,6 @@
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
-            RecentsTransitionHandler recentsTransitionHandler,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
         if (DesktopModeStatus.isEnabled()) {
             return new DesktopModeWindowDecorViewModel(
@@ -226,7 +225,6 @@
                     syncQueue,
                     transitions,
                     desktopTasksController,
-                    recentsTransitionHandler,
                     rootTaskDisplayAreaOrganizer);
         }
         return new CaptionWindowDecorViewModel(
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 4a1bcaa..b1c43c1 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
@@ -320,9 +320,8 @@
     }
 
     /** Move a task with given `taskId` to fullscreen */
-    fun moveToFullscreen(taskId: Int, windowDecor: DesktopModeWindowDecoration) {
+    fun moveToFullscreen(taskId: Int) {
         shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task ->
-            windowDecor.incrementRelayoutBlock()
             moveToFullscreenWithAnimation(task, task.positionInParent)
         }
     }
@@ -906,20 +905,17 @@
      * @param position position of surface when drag ends.
      * @param inputCoordinate the coordinates of the motion event
      * @param taskBounds the updated bounds of the task being dragged.
-     * @param windowDecor the window decoration for the task being dragged
      */
     fun onDragPositioningEnd(
         taskInfo: RunningTaskInfo,
         position: Point,
         inputCoordinate: PointF,
-        taskBounds: Rect,
-        windowDecor: DesktopModeWindowDecoration
+        taskBounds: Rect
     ) {
         if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
             return
         }
         if (taskBounds.top <= transitionAreaHeight) {
-            windowDecor.incrementRelayoutBlock()
             moveToFullscreenWithAnimation(taskInfo, position)
         }
         if (inputCoordinate.x <= transitionAreaWidth) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index 9debb25..0218493 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -54,8 +54,6 @@
         taskId: Int,
         windowDecoration: DesktopModeWindowDecoration
     ) {
-        // Pause relayout until the transition animation finishes.
-        windowDecoration.incrementRelayoutBlock()
         transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
         taskToDecorationMap.put(taskId, windowDecoration)
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index 6b6a7bc..ffcc526 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -112,7 +112,6 @@
                     onChangeTransitionReady(change, startT, finishT);
                     break;
             }
-            mWindowDecorViewModel.onTransitionReady(transition, info, change);
         }
         mTransitionToTaskInfo.put(transition, taskInfoList);
     }
@@ -153,8 +152,6 @@
 
     @Override
     public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
-        mWindowDecorViewModel.onTransitionMerged(merged, playing);
-
         final List<ActivityManager.RunningTaskInfo> infoOfMerged =
                 mTransitionToTaskInfo.get(merged);
         if (infoOfMerged == null) {
@@ -178,7 +175,6 @@
         final List<ActivityManager.RunningTaskInfo> taskInfo =
                 mTransitionToTaskInfo.getOrDefault(transition, Collections.emptyList());
         mTransitionToTaskInfo.remove(transition);
-        mWindowDecorViewModel.onTransitionFinished(transition);
         for (int i = 0; i < taskInfo.size(); ++i) {
             mWindowDecorViewModel.destroyWindowDecoration(taskInfo.get(i));
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
index 3906599..8b3de62 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl
@@ -52,9 +52,10 @@
      * @param componentName ComponentName represents the Activity
      * @param destinationBounds the destination bounds the PiP window lands into
      * @param overlay an optional overlay to fade out after entering PiP
+     * @param appBounds the bounds used to set the buffer size of the optional content overlay
      */
     oneway void stopSwipePipToHome(int taskId, in ComponentName componentName,
-            in Rect destinationBounds, in SurfaceControl overlay) = 2;
+            in Rect destinationBounds, in SurfaceControl overlay, in Rect appBounds) = 2;
 
     /**
      * Notifies the swiping Activity to PiP onto home transition is aborted
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 3635165..a9a3f78 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
@@ -334,6 +334,16 @@
     @Nullable
     SurfaceControl mPipOverlay;
 
+    /**
+     * The app bounds used for the buffer size of the
+     * {@link com.android.wm.shell.pip.PipContentOverlay.PipAppIconOverlay}.
+     *
+     * Note that this is empty if the overlay is removed or if it's some other type of overlay
+     * defined in {@link PipContentOverlay}.
+     */
+    @NonNull
+    final Rect mAppBounds = new Rect();
+
     public PipTaskOrganizer(Context context,
             @NonNull SyncTransactionQueue syncTransactionQueue,
             @NonNull PipTransitionState pipTransitionState,
@@ -464,15 +474,15 @@
      * Expect {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)} afterwards.
      */
     public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
-            SurfaceControl overlay) {
+            SurfaceControl overlay, Rect appBounds) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
-                "stopSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
+                "stopSwipePipToHome: %s, stat=%s", componentName, mPipTransitionState);
         // do nothing if there is no startSwipePipToHome being called before
         if (!mPipTransitionState.getInSwipePipToHomeTransition()) {
             return;
         }
         mPipBoundsState.setBounds(destinationBounds);
-        mPipOverlay = overlay;
+        setContentOverlay(overlay, appBounds);
         if (ENABLE_SHELL_TRANSITIONS && overlay != null) {
             // With Shell transition, the overlay was attached to the remote transition leash, which
             // will be removed when the current transition is finished, so we need to reparent it
@@ -1888,7 +1898,7 @@
                         "%s: trying to remove overlay (%s) which is not local reference (%s)",
                         TAG, surface, mPipOverlay);
             }
-            mPipOverlay = null;
+            clearContentOverlay();
         }
         if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
             // Avoid double removal, which is fatal.
@@ -1905,6 +1915,20 @@
         if (callback != null) callback.run();
     }
 
+    void clearContentOverlay() {
+        mPipOverlay = null;
+        mAppBounds.setEmpty();
+    }
+
+    void setContentOverlay(@Nullable SurfaceControl leash, @NonNull Rect appBounds) {
+        mPipOverlay = leash;
+        if (mPipOverlay != null) {
+            mAppBounds.set(appBounds);
+        } else {
+            mAppBounds.setEmpty();
+        }
+    }
+
     private void resetShadowRadius() {
         if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
             // mLeash is undefined when in PipTransitionState.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 f5f15d8..89dcc4c 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
@@ -141,8 +141,6 @@
     /** Whether the PIP window has fade out for fixed rotation. */
     private boolean mHasFadeOut;
 
-    private Rect mInitBounds = new Rect();
-
     /** Used for setting transform to a transaction from animator. */
     private final PipAnimationController.PipTransactionHandler mTransactionConsumer =
             new PipAnimationController.PipTransactionHandler() {
@@ -465,12 +463,13 @@
                     mSurfaceTransactionHelper.crop(tx, leash, destinationBounds)
                             .resetScale(tx, leash, destinationBounds)
                             .round(tx, leash, true /* applyCornerRadius */);
-                    if (mPipOrganizer.mPipOverlay != null && !mInitBounds.isEmpty()) {
+                    final Rect appBounds = mPipOrganizer.mAppBounds;
+                    if (mPipOrganizer.mPipOverlay != null && !appBounds.isEmpty()) {
                         // Resetting the scale for pinned task while re-adjusting its crop,
                         // also scales the overlay. So we need to update the overlay leash too.
                         Rect overlayBounds = new Rect(destinationBounds);
                         final int overlaySize = PipContentOverlay.PipAppIconOverlay
-                                .getOverlaySize(mInitBounds, destinationBounds);
+                                .getOverlaySize(appBounds, destinationBounds);
 
                         overlayBounds.offsetTo(
                                 (destinationBounds.width() - overlaySize) / 2,
@@ -479,7 +478,6 @@
                                 mPipOrganizer.mPipOverlay, overlayBounds);
                     }
                 }
-                mInitBounds.setEmpty();
                 wct.setBoundsChangeTransaction(taskInfo.token, tx);
             }
             final int displayRotation = taskInfo.getConfiguration().windowConfiguration
@@ -617,7 +615,7 @@
         // if overlay is present remove it immediately, as exit transition came before it faded out
         if (mPipOrganizer.mPipOverlay != null) {
             startTransaction.remove(mPipOrganizer.mPipOverlay);
-            clearPipOverlay();
+            mPipOrganizer.clearContentOverlay();
         }
         if (pipChange == null) {
             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -951,9 +949,6 @@
         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         final Rect currentBounds = pipChange.getStartAbsBounds();
 
-        // Cache the start bounds for overlay manipulations as a part of finishCallback.
-        mInitBounds.set(currentBounds);
-
         int rotationDelta = deltaRotation(startRotation, endRotation);
         Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
                 taskInfo.pictureInPictureParams, currentBounds, destinationBounds);
@@ -1022,7 +1017,7 @@
         } else {
             throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
         }
-        mPipOrganizer.mPipOverlay = animator.getContentOverlayLeash();
+        mPipOrganizer.setContentOverlay(animator.getContentOverlayLeash(), currentBounds);
         animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
                 .setPipAnimationCallback(mPipAnimationCallback)
                 .setDuration(mEnterExitAnimationDuration);
@@ -1073,10 +1068,6 @@
             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                     "%s: SwipePipToHome should not use fixed rotation %d", TAG, mEndFixedRotation);
         }
-        Rect appBounds = pipTaskInfo.configuration.windowConfiguration.getAppBounds();
-        if (mFixedRotationState == FIXED_ROTATION_CALLBACK && appBounds != null) {
-            mInitBounds.set(appBounds);
-        }
         final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mPipOverlay;
         if (swipePipToHomeOverlay != null) {
             // Launcher fade in the overlay on top of the fullscreen Task. It is possible we
@@ -1106,7 +1097,7 @@
         sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
         if (swipePipToHomeOverlay != null) {
             mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay,
-                    this::clearPipOverlay /* callback */, false /* withStartDelay */);
+                    null /* callback */, false /* withStartDelay */);
         }
         mPipTransitionState.setInSwipePipToHomeTransition(false);
     }
@@ -1250,10 +1241,6 @@
         mPipMenuController.updateMenuBounds(destinationBounds);
     }
 
-    private void clearPipOverlay() {
-        mPipOrganizer.mPipOverlay = null;
-    }
-
     @Override
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 63f20fd..238e6b5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -982,8 +982,9 @@
     }
 
     private void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
-            SurfaceControl overlay) {
-        mPipTaskOrganizer.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay);
+            SurfaceControl overlay, Rect appBounds) {
+        mPipTaskOrganizer.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay,
+                appBounds);
     }
 
     private void abortSwipePipToHome(int taskId, ComponentName componentName) {
@@ -1280,13 +1281,13 @@
 
         @Override
         public void stopSwipePipToHome(int taskId, ComponentName componentName,
-                Rect destinationBounds, SurfaceControl overlay) {
+                Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
             if (overlay != null) {
                 overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome");
             }
             executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
                     (controller) -> controller.stopSwipePipToHome(
-                            taskId, componentName, destinationBounds, overlay));
+                            taskId, componentName, destinationBounds, overlay, appBounds));
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 7b57097..880d952 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -23,12 +23,15 @@
 import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI;
 
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.common.split.SplitScreenUtils.getComponent;
+import static com.android.wm.shell.common.split.SplitScreenUtils.getShortcutComponent;
 import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
 import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage;
@@ -47,6 +50,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
 import android.content.pm.ShortcutInfo;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -171,6 +176,8 @@
     private final ShellTaskOrganizer mTaskOrganizer;
     private final SyncTransactionQueue mSyncQueue;
     private final Context mContext;
+    private final PackageManager mPackageManager;
+    private final LauncherApps mLauncherApps;
     private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
     private final ShellExecutor mMainExecutor;
     private final SplitScreenImpl mImpl = new SplitScreenImpl();
@@ -186,7 +193,8 @@
     private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
     private final Optional<DesktopTasksController> mDesktopTasksController;
     private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
-    private final String[] mAppsSupportMultiInstances;
+    // A static allow list of apps which support multi-instance
+    private final String[] mAppsSupportingMultiInstance;
 
     @VisibleForTesting
     StageCoordinator mStageCoordinator;
@@ -220,6 +228,8 @@
         mTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
         mContext = context;
+        mPackageManager = context.getPackageManager();
+        mLauncherApps = context.getSystemService(LauncherApps.class);
         mRootTDAOrganizer = rootTDAOrganizer;
         mMainExecutor = mainExecutor;
         mDisplayController = displayController;
@@ -242,7 +252,7 @@
 
         // TODO(255224696): Remove the config once having a way for client apps to opt-in
         //                  multi-instances split.
-        mAppsSupportMultiInstances = mContext.getResources()
+        mAppsSupportingMultiInstance = mContext.getResources()
                 .getStringArray(R.array.config_appsSupportMultiInstancesSplit);
     }
 
@@ -266,12 +276,15 @@
             WindowDecorViewModel windowDecorViewModel,
             DesktopTasksController desktopTasksController,
             ShellExecutor mainExecutor,
-            StageCoordinator stageCoordinator) {
+            StageCoordinator stageCoordinator,
+            String[] appsSupportingMultiInstance) {
         mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
         mTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
         mContext = context;
+        mPackageManager = context.getPackageManager();
+        mLauncherApps = context.getSystemService(LauncherApps.class);
         mRootTDAOrganizer = rootTDAOrganizer;
         mMainExecutor = mainExecutor;
         mDisplayController = displayController;
@@ -288,8 +301,7 @@
         mStageCoordinator = stageCoordinator;
         mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
         shellInit.addInitCallback(this::onInit, this);
-        mAppsSupportMultiInstances = mContext.getResources()
-                .getStringArray(R.array.config_appsSupportMultiInstancesSplit);
+        mAppsSupportingMultiInstance = appsSupportingMultiInstance;
     }
 
     public SplitScreen asSplitScreen() {
@@ -588,7 +600,8 @@
 
         if (samePackage(packageName, getPackageName(reverseSplitPosition(position)),
                 user.getIdentifier(), getUserId(reverseSplitPosition(position)))) {
-            if (supportMultiInstancesSplit(packageName)) {
+            if (supportsMultiInstanceSplit(getShortcutComponent(packageName, shortcutId, user,
+                    mLauncherApps))) {
                 activityOptions.setApplyMultipleTaskFlagForShortcut(true);
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
             } else if (isSplitScreenVisible()) {
@@ -609,7 +622,7 @@
                 activityOptions.toBundle(), user);
     }
 
-    void startShortcutAndTaskWithLegacyTransition(ShortcutInfo shortcutInfo,
+    void startShortcutAndTaskWithLegacyTransition(@NonNull ShortcutInfo shortcutInfo,
             @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
             @SplitPosition int splitPosition, @PersistentSnapPosition int snapPosition,
             RemoteAnimationAdapter adapter, InstanceId instanceId) {
@@ -621,7 +634,7 @@
         final int userId1 = shortcutInfo.getUserId();
         final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
         if (samePackage(packageName1, packageName2, userId1, userId2)) {
-            if (supportMultiInstancesSplit(shortcutInfo.getPackage())) {
+            if (supportsMultiInstanceSplit(shortcutInfo.getActivity())) {
                 activityOptions.setApplyMultipleTaskFlagForShortcut(true);
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
             } else {
@@ -640,7 +653,7 @@
                 instanceId);
     }
 
-    void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
+    void startShortcutAndTask(@NonNull ShortcutInfo shortcutInfo, @Nullable Bundle options1,
             int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
             @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition,
             InstanceId instanceId) {
@@ -653,7 +666,7 @@
         final int userId1 = shortcutInfo.getUserId();
         final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
         if (samePackage(packageName1, packageName2, userId1, userId2)) {
-            if (supportMultiInstancesSplit(packageName1)) {
+            if (supportsMultiInstanceSplit(shortcutInfo.getActivity())) {
                 activityOptions.setApplyMultipleTaskFlagForShortcut(true);
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
             } else {
@@ -692,7 +705,7 @@
         final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
         final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
         if (samePackage(packageName1, packageName2, userId1, userId2)) {
-            if (supportMultiInstancesSplit(packageName1)) {
+            if (supportsMultiInstanceSplit(getComponent(pendingIntent))) {
                 fillInIntent = new Intent();
                 fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
@@ -722,7 +735,7 @@
         final int userId2 = SplitScreenUtils.getUserId(taskId, mTaskOrganizer);
         boolean setSecondIntentMultipleTask = false;
         if (samePackage(packageName1, packageName2, userId1, userId2)) {
-            if (supportMultiInstancesSplit(packageName1)) {
+            if (supportsMultiInstanceSplit(getComponent(pendingIntent))) {
                 setSecondIntentMultipleTask = true;
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
             } else {
@@ -757,7 +770,7 @@
         final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1);
         final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2);
         if (samePackage(packageName1, packageName2, userId1, userId2)) {
-            if (supportMultiInstancesSplit(packageName1)) {
+            if (supportsMultiInstanceSplit(getComponent(pendingIntent1))) {
                 fillInIntent1 = new Intent();
                 fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                 fillInIntent2 = new Intent();
@@ -794,7 +807,7 @@
                 ? ActivityOptions.fromBundle(options2) : ActivityOptions.makeBasic();
         boolean setSecondIntentMultipleTask = false;
         if (samePackage(packageName1, packageName2, userId1, userId2)) {
-            if (supportMultiInstancesSplit(packageName1)) {
+            if (supportsMultiInstanceSplit(getComponent(pendingIntent1))) {
                 fillInIntent1 = new Intent();
                 fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
                 setSecondIntentMultipleTask = true;
@@ -856,7 +869,7 @@
             return;
         }
         if (samePackage(packageName1, packageName2, userId1, userId2)) {
-            if (supportMultiInstancesSplit(packageName1)) {
+            if (supportsMultiInstanceSplit(getComponent(intent))) {
                 // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
                 // the split and there is no reusable background task.
                 fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -915,16 +928,63 @@
         return taskInfo != null ? taskInfo.userId : -1;
     }
 
+    /**
+     * Returns whether a specific component desires to be launched in multiple instances for
+     * split screen.
+     */
     @VisibleForTesting
-    boolean supportMultiInstancesSplit(String packageName) {
-        if (packageName != null) {
-            for (int i = 0; i < mAppsSupportMultiInstances.length; i++) {
-                if (mAppsSupportMultiInstances[i].equals(packageName)) {
-                    return true;
-                }
+    boolean supportsMultiInstanceSplit(@Nullable ComponentName componentName) {
+        if (componentName == null || componentName.getPackageName() == null) {
+            // TODO(b/262864589): Handle empty component case
+            return false;
+        }
+
+        // Check the pre-defined allow list
+        final String packageName = componentName.getPackageName();
+        for (int i = 0; i < mAppsSupportingMultiInstance.length; i++) {
+            if (mAppsSupportingMultiInstance[i].equals(packageName)) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "application=%s in allowlist supports multi-instance", packageName);
+                return true;
             }
         }
 
+        // Check the activity property first
+        try {
+            final PackageManager.Property activityProp = mPackageManager.getProperty(
+                    PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, componentName);
+            // If the above call doesn't throw a NameNotFoundException, then the activity property
+            // should override the application property value
+            if (activityProp.isBoolean()) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "activity=%s supports multi-instance", componentName);
+                return activityProp.getBoolean();
+            } else {
+                ProtoLog.w(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "Warning: property=%s for activity=%s has non-bool type=%d",
+                        PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName,
+                        activityProp.getType());
+            }
+        } catch (PackageManager.NameNotFoundException nnfe) {
+            // Not specified in the activity, fall through
+        }
+
+        // Check the application property otherwise
+        try {
+            final PackageManager.Property appProp = mPackageManager.getProperty(
+                    PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName);
+            if (appProp.isBoolean()) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "application=%s supports multi-instance", packageName);
+                return appProp.getBoolean();
+            } else {
+                ProtoLog.w(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "Warning: property=%s for application=%s has non-bool type=%d",
+                        PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, packageName, appProp.getType());
+            }
+        } catch (PackageManager.NameNotFoundException nnfe) {
+            // Not specified in either application or activity
+        }
         return false;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index f58aeac..a666e20 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -73,6 +73,7 @@
 import com.android.internal.graphics.palette.Palette;
 import com.android.internal.graphics.palette.Quantizer;
 import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
+import com.android.internal.policy.PhoneWindow;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.IconProvider;
@@ -245,16 +246,19 @@
         } else {
             windowFlags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         }
-        params.layoutInDisplayCutoutMode = a.getInt(
-                R.styleable.Window_windowLayoutInDisplayCutoutMode,
-                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS);
-        params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
-        a.recycle();
 
         final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
         final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
                 ? windowInfo.targetActivityInfo
                 : taskInfo.topActivityInfo;
+        params.layoutInDisplayCutoutMode = a.getInt(
+                R.styleable.Window_windowLayoutInDisplayCutoutMode,
+                PhoneWindow.isEdgeToEdgeEnforced(activityInfo.applicationInfo, false /* local */)
+                        ? WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+                        : params.layoutInDisplayCutoutMode);
+        params.windowAnimations = a.getResourceId(R.styleable.Window_windowAnimationStyle, 0);
+        a.recycle();
+
         final int displayId = taskInfo.displayId;
         // Assumes it's safe to show starting windows of launched apps while
         // the keyguard is being hidden. This is okay because starting windows never show
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 9f20f49..db84513 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -21,9 +21,9 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
 import static android.view.WindowManager.TRANSIT_PIP;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 
@@ -84,7 +84,7 @@
     private UnfoldTransitionHandler mUnfoldHandler;
     private ActivityEmbeddingController mActivityEmbeddingController;
 
-    private class MixedTransition {
+    private static class MixedTransition {
         static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
 
         /** Both the display and split-state (enter/exit) is changing */
@@ -175,7 +175,6 @@
             joinFinishArgs(wct);
 
             if (mInFlightSubAnimations == 0) {
-                mActiveTransitions.remove(MixedTransition.this);
                 mFinishCB.onTransitionFinished(mFinishWCT);
             }
         }
@@ -401,8 +400,12 @@
                 final MixedTransition keyguardMixed =
                         new MixedTransition(MixedTransition.TYPE_KEYGUARD, transition);
                 mActiveTransitions.add(keyguardMixed);
-                final boolean hasAnimateKeyguard = animateKeyguard(keyguardMixed, info,
-                        startTransaction, finishTransaction, finishCallback);
+                Transitions.TransitionFinishCallback callback = wct -> {
+                    mActiveTransitions.remove(keyguardMixed);
+                    finishCallback.onTransitionFinished(wct);
+                };
+                final boolean hasAnimateKeyguard = animateKeyguard(
+                        keyguardMixed, info, startTransaction, finishTransaction, callback);
                 if (hasAnimateKeyguard) {
                     ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                             "Converting mixed transition into a keyguard transition");
@@ -420,27 +423,34 @@
 
         if (mixed == null) return false;
 
-        if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
-            return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
-                    finishCallback);
-        } else if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
-            return animateEnterPipFromActivityEmbedding(mixed, info, startTransaction,
-                    finishTransaction, finishCallback);
-        } else if (mixed.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
+        final MixedTransition chosenTransition = mixed;
+        Transitions.TransitionFinishCallback callback = wct -> {
+            mActiveTransitions.remove(chosenTransition);
+            finishCallback.onTransitionFinished(wct);
+        };
+
+        if (chosenTransition.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
+            return animateEnterPipFromSplit(
+                    chosenTransition, info, startTransaction, finishTransaction, callback);
+        } else if (chosenTransition.mType
+                == MixedTransition.TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING) {
+            return animateEnterPipFromActivityEmbedding(
+                    chosenTransition, info, startTransaction, finishTransaction, callback);
+        } else if (chosenTransition.mType == MixedTransition.TYPE_DISPLAY_AND_SPLIT_CHANGE) {
             return false;
-        } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
-            final boolean handledToPip = animateOpenIntentWithRemoteAndPip(mixed, info,
-                    startTransaction, finishTransaction, finishCallback);
+        } else if (chosenTransition.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) {
+            final boolean handledToPip = animateOpenIntentWithRemoteAndPip(
+                    chosenTransition, info, startTransaction, finishTransaction, callback);
             // Consume the transition on remote handler if the leftover handler already handle this
             // transition. And if it cannot, the transition will be handled by remote handler, so
             // don't consume here.
             // Need to check leftOverHandler as it may change in #animateOpenIntentWithRemoteAndPip
-            if (handledToPip && mixed.mHasRequestToRemote
-                    && mixed.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
+            if (handledToPip && chosenTransition.mHasRequestToRemote
+                    && chosenTransition.mLeftoversHandler != mPlayer.getRemoteTransitionHandler()) {
                 mPlayer.getRemoteTransitionHandler().onTransitionConsumed(transition, false, null);
             }
             return handledToPip;
-        } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
+        } else if (chosenTransition.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
             for (int i = info.getChanges().size() - 1; i >= 0; --i) {
                 final TransitionInfo.Change change = info.getChanges().get(i);
                 // Pip auto-entering info might be appended to recent transition like pressing
@@ -449,28 +459,29 @@
                 if (mPipHandler.isEnteringPip(change, info.getType())
                         && mSplitHandler.getSplitItemPosition(change.getLastParent())
                         != SPLIT_POSITION_UNDEFINED) {
-                    return animateEnterPipFromSplit(mixed, info, startTransaction,
-                            finishTransaction, finishCallback);
+                    return animateEnterPipFromSplit(
+                            chosenTransition, info, startTransaction, finishTransaction, callback);
                 }
             }
 
-            return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction,
-                    finishCallback);
-        } else if (mixed.mType == MixedTransition.TYPE_KEYGUARD) {
-            return animateKeyguard(mixed, info, startTransaction, finishTransaction,
-                    finishCallback);
-        } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_KEYGUARD) {
-            return animateRecentsDuringKeyguard(mixed, info, startTransaction, finishTransaction,
-                    finishCallback);
-        } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
-            return animateRecentsDuringDesktop(mixed, info, startTransaction, finishTransaction,
-                    finishCallback);
-        } else if (mixed.mType == MixedTransition.TYPE_UNFOLD) {
-            return animateUnfold(mixed, info, startTransaction, finishTransaction, finishCallback);
+            return animateRecentsDuringSplit(
+                    chosenTransition, info, startTransaction, finishTransaction, callback);
+        } else if (chosenTransition.mType == MixedTransition.TYPE_KEYGUARD) {
+            return animateKeyguard(
+                    chosenTransition, info, startTransaction, finishTransaction, callback);
+        } else if (chosenTransition.mType == MixedTransition.TYPE_RECENTS_DURING_KEYGUARD) {
+            return animateRecentsDuringKeyguard(
+                    chosenTransition, info, startTransaction, finishTransaction, callback);
+        } else if (chosenTransition.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+            return animateRecentsDuringDesktop(
+                    chosenTransition, info, startTransaction, finishTransaction, callback);
+        } else if (chosenTransition.mType == MixedTransition.TYPE_UNFOLD) {
+            return animateUnfold(
+                    chosenTransition, info, startTransaction, finishTransaction, callback);
         } else {
-            mActiveTransitions.remove(mixed);
+            mActiveTransitions.remove(chosenTransition);
             throw new IllegalStateException("Starting mixed animation without a known mixed type? "
-                    + mixed.mType);
+                    + chosenTransition.mType);
         }
     }
 
@@ -727,7 +738,11 @@
         final MixedTransition mixed = new MixedTransition(
                 MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition);
         mActiveTransitions.add(mixed);
-        return animateEnterPipFromSplit(mixed, info, startT, finishT, finishCallback);
+        Transitions.TransitionFinishCallback callback = wct -> {
+            mActiveTransitions.remove(mixed);
+            finishCallback.onTransitionFinished(wct);
+        };
+        return animateEnterPipFromSplit(mixed, info, startT, finishT, callback);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index cebc400..1a793a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -23,13 +23,11 @@
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 import android.os.Handler;
-import android.os.IBinder;
 import android.util.SparseArray;
 import android.view.Choreographer;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
 import android.view.View;
-import android.window.TransitionInfo;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -80,16 +78,6 @@
     }
 
     @Override
-    public void onTransitionReady(IBinder transition, TransitionInfo info,
-            TransitionInfo.Change change) {}
-
-    @Override
-    public void onTransitionMerged(IBinder merged, IBinder playing) {}
-
-    @Override
-    public void onTransitionFinished(IBinder transition) {}
-
-    @Override
     public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) {
         mTaskOperations = new TaskOperations(transitionStarter, mContext, mSyncQueue);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 61a8e9b..d07c646 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -22,7 +22,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.WindowInsets.Type.statusBars;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -43,7 +42,6 @@
 import android.graphics.Region;
 import android.hardware.input.InputManager;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Looper;
 import android.util.SparseArray;
 import android.view.Choreographer;
@@ -59,8 +57,6 @@
 import android.view.SurfaceControl.Transaction;
 import android.view.View;
 import android.view.ViewConfiguration;
-import android.view.WindowManager;
-import android.window.TransitionInfo;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -80,8 +76,6 @@
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
-import com.android.wm.shell.recents.RecentsTransitionHandler;
-import com.android.wm.shell.recents.RecentsTransitionStateListener;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreen.StageType;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -115,7 +109,6 @@
     private final DisplayController mDisplayController;
     private final SyncTransactionQueue mSyncQueue;
     private final Optional<DesktopTasksController> mDesktopTasksController;
-    private final RecentsTransitionHandler mRecentsTransitionHandler;
     private boolean mTransitionDragActive;
 
     private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -154,7 +147,6 @@
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
-            RecentsTransitionHandler recentsTransitionHandler,
             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
     ) {
         this(
@@ -170,7 +162,6 @@
                 syncQueue,
                 transitions,
                 desktopTasksController,
-                recentsTransitionHandler,
                 new DesktopModeWindowDecoration.Factory(),
                 new InputMonitorFactory(),
                 SurfaceControl.Transaction::new,
@@ -191,7 +182,6 @@
             SyncTransactionQueue syncQueue,
             Transitions transitions,
             Optional<DesktopTasksController> desktopTasksController,
-            RecentsTransitionHandler recentsTransitionHandler,
             DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
             InputMonitorFactory inputMonitorFactory,
             Supplier<SurfaceControl.Transaction> transactionFactory,
@@ -207,7 +197,6 @@
         mSyncQueue = syncQueue;
         mTransitions = transitions;
         mDesktopTasksController = desktopTasksController;
-        mRecentsTransitionHandler = recentsTransitionHandler;
         mShellCommandHandler = shellCommandHandler;
         mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
         mInputMonitorFactory = inputMonitorFactory;
@@ -219,12 +208,6 @@
 
     private void onInit() {
         mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
-        mRecentsTransitionHandler.addTransitionStateListener(new RecentsTransitionStateListener() {
-            @Override
-            public void onTransitionStarted(IBinder transition) {
-                blockRelayoutOnTransitionStarted(transition);
-            }
-        });
         mShellCommandHandler.addDumpCallback(this::dump, this);
         mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
                 new DesktopModeOnInsetsChangedListener());
@@ -264,48 +247,6 @@
     }
 
     @Override
-    public void onTransitionReady(
-            @NonNull IBinder transition,
-            @NonNull TransitionInfo info,
-            @NonNull TransitionInfo.Change change) {
-        if (change.getMode() == WindowManager.TRANSIT_CHANGE
-                && (info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE
-                || info.getType() == Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
-                || info.getType() == Transitions.TRANSIT_MOVE_TO_DESKTOP)) {
-            mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
-                    .addTransitionPausingRelayout(transition);
-        } else if (change.getMode() == WindowManager.TRANSIT_TO_BACK
-                && info.getType() == Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
-                && change.getTaskInfo() != null) {
-            final DesktopModeWindowDecoration decor =
-                    mWindowDecorByTaskId.get(change.getTaskInfo().taskId);
-            if (decor != null) {
-                decor.addTransitionPausingRelayout(transition);
-            }
-        } else if (change.getMode() == WindowManager.TRANSIT_TO_FRONT
-                && ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0)
-                && change.getTaskInfo() != null) {
-            blockRelayoutOnTransitionStarted(transition);
-        }
-    }
-
-    @Override
-    public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
-        for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
-            final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
-            decor.mergeTransitionPausingRelayout(merged, playing);
-        }
-    }
-
-    @Override
-    public void onTransitionFinished(@NonNull IBinder transition) {
-        for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
-            final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
-            decor.removeTransitionPausingRelayout(transition);
-        }
-    }
-
-    @Override
     public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
         if (decoration == null) return;
@@ -365,16 +306,6 @@
         }
     }
 
-    private void blockRelayoutOnTransitionStarted(IBinder transition) {
-        // Block relayout on window decorations originating from #onTaskInfoChanges until the
-        // animation completes to avoid interfering with the transition animation.
-        for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
-            final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
-            decor.incrementRelayoutBlock();
-            decor.addTransitionPausingRelayout(transition);
-        }
-    }
-
     private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
             implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
             DragDetector.MotionEventHandler {
@@ -425,7 +356,6 @@
                     // App sometimes draws before the insets from WindowDecoration#relayout have
                     // been added, so they must be added here
                     mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
-                    decoration.incrementRelayoutBlock();
                     mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct);
                     closeOtherSplitTask(mTaskId);
                 }
@@ -436,7 +366,7 @@
                     mSplitScreenController.moveTaskToFullscreen(mTaskId);
                 } else {
                     mDesktopTasksController.ifPresent(c ->
-                            c.moveToFullscreen(mTaskId, mWindowDecorByTaskId.get(mTaskId)));
+                            c.moveToFullscreen(mTaskId));
                 }
             } else if (id == R.id.split_screen_button) {
                 decoration.closeHandleMenu();
@@ -604,7 +534,7 @@
                     mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
                             position,
                             new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
-                            newTaskBounds, mWindowDecorByTaskId.get(mTaskId)));
+                            newTaskBounds));
                     mIsDragging = false;
                     return true;
                 }
@@ -812,20 +742,8 @@
                                     mContext, mDragToDesktopAnimationStartBounds,
                                     relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
                             mDesktopTasksController.ifPresent(
-                                    c -> {
-                                        final int taskId = relevantDecor.mTaskInfo.taskId;
-                                        relevantDecor.incrementRelayoutBlock();
-                                        if (isTaskInSplitScreen(taskId)) {
-                                            final DesktopModeWindowDecoration otherDecor =
-                                                    mWindowDecorByTaskId.get(
-                                                            getOtherSplitTask(taskId).taskId);
-                                            if (otherDecor != null) {
-                                                otherDecor.incrementRelayoutBlock();
-                                            }
-                                        }
-                                        c.startDragToDesktop(relevantDecor.mTaskInfo,
-                                                mMoveToDesktopAnimator, relevantDecor);
-                                    });
+                                    c -> c.startDragToDesktop(relevantDecor.mTaskInfo,
+                                            mMoveToDesktopAnimator, relevantDecor));
                         }
                     }
                     if (mMoveToDesktopAnimator != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index d08b655..5f77192 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -36,7 +36,6 @@
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
-import android.os.IBinder;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.MotionEvent;
@@ -62,8 +61,6 @@
 import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder;
 import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationViewHolder;
 
-import java.util.HashSet;
-import java.util.Set;
 import java.util.function.Supplier;
 
 /**
@@ -104,8 +101,6 @@
 
     private ExclusionRegionListener mExclusionRegionListener;
 
-    private final Set<IBinder> mTransitionsPausingRelayout = new HashSet<>();
-    private int mRelayoutBlock;
     private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
 
     DesktopModeWindowDecoration(
@@ -179,13 +174,6 @@
 
     @Override
     void relayout(ActivityManager.RunningTaskInfo taskInfo) {
-        // TaskListener callbacks and shell transitions aren't synchronized, so starting a shell
-        // transition can trigger an onTaskInfoChanged call that updates the task's SurfaceControl
-        // and interferes with the transition animation that is playing at the same time.
-        if (mRelayoutBlock > 0) {
-            return;
-        }
-
         final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
         // The crop and position of the task should only be set when a task is fluid resizing. In
         // all other cases, it is expected that the transition handler positions and crops the task
@@ -737,16 +725,6 @@
         return exclusionRegion;
     }
 
-    /**
-     * If transition exists in mTransitionsPausingRelayout, remove the transition and decrement
-     * mRelayoutBlock
-     */
-    void removeTransitionPausingRelayout(IBinder transition) {
-        if (mTransitionsPausingRelayout.remove(transition)) {
-            mRelayoutBlock--;
-        }
-    }
-
     @Override
     int getCaptionHeightId(@WindowingMode int windowingMode) {
         return getCaptionHeightIdStatic(windowingMode);
@@ -767,35 +745,10 @@
         return R.id.desktop_mode_caption;
     }
 
-    /**
-     * Add transition to mTransitionsPausingRelayout
-     */
-    void addTransitionPausingRelayout(IBinder transition) {
-        mTransitionsPausingRelayout.add(transition);
-    }
-
-    /**
-     * If two transitions merge and the merged transition is in mTransitionsPausingRelayout,
-     * remove the merged transition from the set and add the transition it was merged into.
-     */
-    public void mergeTransitionPausingRelayout(IBinder merged, IBinder playing) {
-        if (mTransitionsPausingRelayout.remove(merged)) {
-            mTransitionsPausingRelayout.add(playing);
-        }
-    }
-
-    /**
-     * Increase mRelayoutBlock, stopping relayout if mRelayoutBlock is now greater than 0.
-     */
-    public void incrementRelayoutBlock() {
-        mRelayoutBlock++;
-    }
-
     @Override
     public String toString() {
         return "{"
                 + "mPositionInParent=" + mPositionInParent + ", "
-                + "mRelayoutBlock=" + mRelayoutBlock + ", "
                 + "taskId=" + mTaskInfo.taskId + ", "
                 + "windowingMode=" + windowingModeToString(mTaskInfo.getWindowingMode()) + ", "
                 + "isFocused=" + isFocused()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index ae1a3d9..01a6012 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -17,9 +17,7 @@
 package com.android.wm.shell.windowdecor;
 
 import android.app.ActivityManager;
-import android.os.IBinder;
 import android.view.SurfaceControl;
-import android.window.TransitionInfo;
 
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -103,34 +101,4 @@
      * @param taskInfo the info of the task
      */
     void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
-
-    /**
-     * Notifies that a shell transition is about to start. If the transition is of type
-     * TRANSIT_ENTER_DESKTOP, it will save that transition to unpause relayout for the transitioning
-     * task after the transition has ended.
-     *
-     * @param transition the ready transaction
-     * @param info of Transition to check if relayout needs to be paused for a task
-     * @param change a change in the given transition
-     */
-    default void onTransitionReady(IBinder transition, TransitionInfo info,
-            TransitionInfo.Change change) {}
-
-    /**
-     * Notifies that a shell transition is about to merge with another to give the window
-     * decoration a chance to prepare for this merge.
-     *
-     * @param merged the transaction being merged
-     * @param playing the transaction being merged into
-     */
-    default void onTransitionMerged(IBinder merged, IBinder playing) {}
-
-    /**
-     * Notifies that a shell transition is about to finish  to give the window decoration a chance
-     * to clean up.
-     *
-     * @param transaction
-     */
-    default void onTransitionFinished(IBinder transaction) {}
-
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenUtilsTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenUtilsTests.kt
new file mode 100644
index 0000000..955660c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenUtilsTests.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.wm.shell.common.split
+
+import android.content.ComponentName
+import android.content.pm.LauncherApps
+import android.content.pm.ShortcutInfo
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.wm.shell.ShellTestCase
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.`when`
+
+@RunWith(AndroidJUnit4::class)
+class SplitScreenUtilsTests : ShellTestCase() {
+
+    @Test
+    fun getShortcutComponent_nullShortcuts() {
+        val launcherApps = mock(LauncherApps::class.java).also {
+            `when`(it.getShortcuts(any(), any())).thenReturn(null)
+        }
+        assertEquals(null, SplitScreenUtils.getShortcutComponent(TEST_PACKAGE,
+                TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps))
+    }
+
+    @Test
+    fun getShortcutComponent_noShortcuts() {
+        val launcherApps = mock(LauncherApps::class.java).also {
+            `when`(it.getShortcuts(any(), any())).thenReturn(ArrayList<ShortcutInfo>())
+        }
+        assertEquals(null, SplitScreenUtils.getShortcutComponent(TEST_PACKAGE,
+                TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps))
+    }
+
+    @Test
+    fun getShortcutComponent_validShortcut() {
+        val component = ComponentName(TEST_PACKAGE, TEST_ACTIVITY)
+        val shortcutInfo = ShortcutInfo.Builder(context, "id").setActivity(component).build()
+        val launcherApps = mock(LauncherApps::class.java).also {
+            `when`(it.getShortcuts(any(), any())).thenReturn(arrayListOf(shortcutInfo))
+        }
+        assertEquals(component, SplitScreenUtils.getShortcutComponent(TEST_PACKAGE,
+                TEST_SHORTCUT_ID, UserHandle.CURRENT, launcherApps))
+    }
+
+    companion object {
+        val TEST_PACKAGE = "com.android.wm.shell.common.split"
+        val TEST_ACTIVITY = "TestActivity";
+        val TEST_SHORTCUT_ID = "test_shortcut_1"
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 94c862b..9249b0a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -393,7 +393,7 @@
     fun moveToFullscreen_displayFullscreen_windowingModeSetToUndefined() {
         val task = setUpFreeformTask()
         task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
-        controller.moveToFullscreen(task.taskId, desktopModeWindowDecoration)
+        controller.moveToFullscreen(task.taskId)
         val wct = getLatestExitDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
             .isEqualTo(WINDOWING_MODE_UNDEFINED)
@@ -403,7 +403,7 @@
     fun moveToFullscreen_displayFreeform_windowingModeSetToFullscreen() {
         val task = setUpFreeformTask()
         task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
-        controller.moveToFullscreen(task.taskId, desktopModeWindowDecoration)
+        controller.moveToFullscreen(task.taskId)
         val wct = getLatestExitDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
                 .isEqualTo(WINDOWING_MODE_FULLSCREEN)
@@ -411,7 +411,7 @@
 
     @Test
     fun moveToFullscreen_nonExistentTask_doesNothing() {
-        controller.moveToFullscreen(999, desktopModeWindowDecoration)
+        controller.moveToFullscreen(999)
         verifyWCTNotExecuted()
     }
 
@@ -420,7 +420,7 @@
         val taskDefaultDisplay = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
         val taskSecondDisplay = setUpFreeformTask(displayId = SECOND_DISPLAY)
 
-        controller.moveToFullscreen(taskDefaultDisplay.taskId, desktopModeWindowDecoration)
+        controller.moveToFullscreen(taskDefaultDisplay.taskId)
 
         with(getLatestExitDesktopWct()) {
             assertThat(changes.keys).contains(taskDefaultDisplay.token.asBinder())
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 855b7ee..12a5594 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -22,6 +22,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
+import static android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI;
 
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
@@ -36,6 +37,8 @@
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -46,8 +49,10 @@
 import android.app.ActivityTaskManager;
 import android.app.PendingIntent;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 
 import androidx.test.annotation.UiThreadTest;
@@ -55,6 +60,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.R;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
@@ -91,6 +97,10 @@
 @RunWith(AndroidJUnit4.class)
 public class SplitScreenControllerTests extends ShellTestCase {
 
+    private static final String TEST_PACKAGE = "com.android.wm.shell.splitscreen";
+    private static final String TEST_NOT_ALLOWED_PACKAGE = "com.android.wm.shell.splitscreen.fake";
+    private static final String TEST_ACTIVITY = "TestActivity";
+
     @Mock ShellInit mShellInit;
     @Mock ShellCommandHandler mShellCommandHandler;
     @Mock ShellTaskOrganizer mTaskOrganizer;
@@ -118,6 +128,8 @@
     public void setup() {
         assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
         MockitoAnnotations.initMocks(this);
+        String[] appsSupportingMultiInstance = mContext.getResources()
+                .getStringArray(R.array.config_appsSupportMultiInstancesSplit);
         mShellController = spy(new ShellController(mContext, mShellInit, mShellCommandHandler,
                 mMainExecutor));
         mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
@@ -125,7 +137,8 @@
                 mRootTDAOrganizer, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
                 mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
-                mDesktopTasksController, mMainExecutor, mStageCoordinator));
+                mDesktopTasksController, mMainExecutor, mStageCoordinator,
+                appsSupportingMultiInstance));
     }
 
     @Test
@@ -200,7 +213,7 @@
 
     @Test
     public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() {
-        doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+        doReturn(true).when(mSplitScreenController).supportsMultiInstanceSplit(any());
         Intent startIntent = createStartIntent("startActivity");
         PendingIntent pendingIntent =
                 PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
@@ -237,12 +250,13 @@
 
         verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
                 isNull());
-        verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
+        verify(mSplitScreenController, never()).supportsMultiInstanceSplit(any());
         verify(mStageCoordinator, never()).switchSplitPosition(any());
     }
 
     @Test
     public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
+        doReturn(true).when(mSplitScreenController).supportsMultiInstanceSplit(any());
         doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
         Intent startIntent = createStartIntent("startActivity");
         PendingIntent pendingIntent =
@@ -259,14 +273,14 @@
 
         mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
                 SPLIT_POSITION_TOP_OR_LEFT, null);
-        verify(mSplitScreenController, never()).supportMultiInstancesSplit(any());
+        verify(mSplitScreenController, never()).supportsMultiInstanceSplit(any());
         verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
                 isNull());
     }
 
     @Test
     public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() {
-        doReturn(false).when(mSplitScreenController).supportMultiInstancesSplit(any());
+        doReturn(false).when(mSplitScreenController).supportsMultiInstanceSplit(any());
         Intent startIntent = createStartIntent("startActivity");
         PendingIntent pendingIntent =
                 PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
@@ -283,6 +297,130 @@
         verify(mStageCoordinator).switchSplitPosition(anyString());
     }
 
+    @Test
+    public void supportsMultiInstanceSplit_inStaticAllowList() {
+        String[] allowList = { TEST_PACKAGE };
+        SplitScreenController controller = new SplitScreenController(mContext, mShellInit,
+                mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+                mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+                mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+                mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+                mDesktopTasksController, mMainExecutor, mStageCoordinator,
+                allowList);
+        ComponentName component = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+        assertEquals(true, controller.supportsMultiInstanceSplit(component));
+    }
+
+    @Test
+    public void supportsMultiInstanceSplit_notInStaticAllowList() {
+        String[] allowList = { TEST_PACKAGE };
+        SplitScreenController controller = new SplitScreenController(mContext, mShellInit,
+                mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+                mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+                mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+                mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+                mDesktopTasksController, mMainExecutor, mStageCoordinator,
+                allowList);
+        ComponentName component = new ComponentName(TEST_NOT_ALLOWED_PACKAGE, TEST_ACTIVITY);
+        assertEquals(false, controller.supportsMultiInstanceSplit(component));
+    }
+
+    @Test
+    public void supportsMultiInstanceSplit_activityPropertyTrue()
+            throws PackageManager.NameNotFoundException {
+        Context context = spy(mContext);
+        ComponentName component = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+        PackageManager pm = mock(PackageManager.class);
+        doReturn(pm).when(context).getPackageManager();
+        PackageManager.Property activityProp = new PackageManager.Property("", true, "", "");
+        doReturn(activityProp).when(pm).getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+                eq(component));
+        PackageManager.Property appProp = new PackageManager.Property("", false, "", "");
+        doReturn(appProp).when(pm).getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+                eq(component.getPackageName()));
+
+        SplitScreenController controller = new SplitScreenController(context, mShellInit,
+                mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+                mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+                mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+                mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+                mDesktopTasksController, mMainExecutor, mStageCoordinator,
+                new String[0]);
+        // Expect activity property to override application property
+        assertEquals(true, controller.supportsMultiInstanceSplit(component));
+    }
+
+    @Test
+    public void supportsMultiInstanceSplit_activityPropertyFalseApplicationPropertyTrue()
+            throws PackageManager.NameNotFoundException {
+        Context context = spy(mContext);
+        ComponentName component = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+        PackageManager pm = mock(PackageManager.class);
+        doReturn(pm).when(context).getPackageManager();
+        PackageManager.Property activityProp = new PackageManager.Property("", false, "", "");
+        doReturn(activityProp).when(pm).getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+                eq(component));
+        PackageManager.Property appProp = new PackageManager.Property("", true, "", "");
+        doReturn(appProp).when(pm).getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+                eq(component.getPackageName()));
+
+        SplitScreenController controller = new SplitScreenController(context, mShellInit,
+                mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+                mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+                mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+                mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+                mDesktopTasksController, mMainExecutor, mStageCoordinator,
+                new String[0]);
+        // Expect activity property to override application property
+        assertEquals(false, controller.supportsMultiInstanceSplit(component));
+    }
+
+    @Test
+    public void supportsMultiInstanceSplit_noActivityPropertyApplicationPropertyTrue()
+            throws PackageManager.NameNotFoundException {
+        Context context = spy(mContext);
+        ComponentName component = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+        PackageManager pm = mock(PackageManager.class);
+        doReturn(pm).when(context).getPackageManager();
+        doThrow(PackageManager.NameNotFoundException.class).when(pm).getProperty(
+                eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component));
+        PackageManager.Property appProp = new PackageManager.Property("", true, "", "");
+        doReturn(appProp).when(pm).getProperty(eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI),
+                eq(component.getPackageName()));
+
+        SplitScreenController controller = new SplitScreenController(context, mShellInit,
+                mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+                mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+                mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+                mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+                mDesktopTasksController, mMainExecutor, mStageCoordinator,
+                new String[0]);
+        // Expect fall through to app property
+        assertEquals(true, controller.supportsMultiInstanceSplit(component));
+    }
+
+    @Test
+    public void supportsMultiInstanceSplit_noActivityOrAppProperty()
+            throws PackageManager.NameNotFoundException {
+        Context context = spy(mContext);
+        ComponentName component = new ComponentName(TEST_PACKAGE, TEST_ACTIVITY);
+        PackageManager pm = mock(PackageManager.class);
+        doReturn(pm).when(context).getPackageManager();
+        doThrow(PackageManager.NameNotFoundException.class).when(pm).getProperty(
+                eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component));
+        doThrow(PackageManager.NameNotFoundException.class).when(pm).getProperty(
+                eq(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI), eq(component.getPackageName()));
+
+        SplitScreenController controller = new SplitScreenController(context, mShellInit,
+                mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
+                mRootTDAOrganizer, mDisplayController, mDisplayImeController,
+                mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
+                mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
+                mDesktopTasksController, mMainExecutor, mStageCoordinator,
+                new String[0]);
+        assertEquals(false, controller.supportsMultiInstanceSplit(component));
+    }
+
     private Intent createStartIntent(String activityName) {
         Intent intent = new Intent();
         intent.setComponent(new ComponentName(mContext, activityName));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 883c24e..f84685a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -27,7 +27,6 @@
 import android.hardware.display.DisplayManager
 import android.hardware.display.VirtualDisplay
 import android.os.Handler
-import android.os.IBinder
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.Choreographer
@@ -40,8 +39,6 @@
 import android.view.SurfaceView
 import android.view.WindowInsets.Type.navigationBars
 import android.view.WindowInsets.Type.statusBars
-import android.view.WindowManager
-import android.window.TransitionInfo
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
@@ -53,8 +50,6 @@
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.desktopmode.DesktopTasksController
-import com.android.wm.shell.recents.RecentsTransitionHandler
-import com.android.wm.shell.recents.RecentsTransitionStateListener
 import com.android.wm.shell.sysui.KeyguardChangeListener
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
@@ -100,7 +95,6 @@
     @Mock private lateinit var mockShellController: ShellController
     @Mock private lateinit var mockShellExecutor: ShellExecutor
     @Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
-    @Mock private lateinit var mockRecentsTransitionHandler: RecentsTransitionHandler
     @Mock private lateinit var mockShellCommandHandler: ShellCommandHandler
 
     private val transactionFactory = Supplier<SurfaceControl.Transaction> {
@@ -127,7 +121,6 @@
                 mockSyncQueue,
                 mockTransitions,
                 Optional.of(mockDesktopTasksController),
-                mockRecentsTransitionHandler,
                 mockDesktopModeWindowDecorFactory,
                 mockInputMonitorFactory,
                 transactionFactory,
@@ -275,48 +268,6 @@
     }
 
     @Test
-    fun testRelayoutBlockedDuringRecentsTransition() {
-        val recentsCaptor = argumentCaptor<RecentsTransitionStateListener>()
-        verify(mockRecentsTransitionHandler).addTransitionStateListener(recentsCaptor.capture())
-
-        val transition = mock(IBinder::class.java)
-        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
-        val decoration = setUpMockDecorationForTask(task)
-
-        // Make sure a window decorations exists first by launching a freeform task.
-        onTaskOpening(task)
-        // Now call back when a Recents transition starts.
-        recentsCaptor.firstValue.onTransitionStarted(transition)
-
-        verify(decoration).incrementRelayoutBlock()
-        verify(decoration).addTransitionPausingRelayout(transition)
-    }
-
-    @Test
-    fun testRelayoutBlockedDuringKeyguardTransition() {
-        val transition = mock(IBinder::class.java)
-        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
-        val decoration = setUpMockDecorationForTask(task)
-        val transitionInfo = mock(TransitionInfo::class.java)
-        val transitionChange = mock(TransitionInfo.Change::class.java)
-        val taskInfo = mock(RunningTaskInfo()::class.java)
-
-        // Replicate a keyguard going away transition for a task
-        whenever(transitionInfo.getFlags())
-                .thenReturn(WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY)
-        whenever(transitionChange.getMode()).thenReturn(WindowManager.TRANSIT_TO_FRONT)
-        whenever(transitionChange.getTaskInfo()).thenReturn(taskInfo)
-
-        // Make sure a window decorations exists first by launching a freeform task.
-        onTaskOpening(task)
-        // OnTransition ready is called when a keyguard going away transition happens
-        desktopModeWindowDecorViewModel
-                .onTransitionReady(transition, transitionInfo, transitionChange)
-
-        verify(decoration).incrementRelayoutBlock()
-        verify(decoration).addTransitionPausingRelayout(transition)
-    }
-    @Test
     fun testRelayoutRunsWhenStatusBarsInsetsSourceVisibilityChanges() {
         val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM, focused = true)
         val decoration = setUpMockDecorationForTask(task)
diff --git a/location/api/current.txt b/location/api/current.txt
index 0c23d8c..c55676b 100644
--- a/location/api/current.txt
+++ b/location/api/current.txt
@@ -414,7 +414,7 @@
     field public static final int TYPE_GPS_L5CNAV = 259; // 0x103
     field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L1 = 1795; // 0x703
     field @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1) public static final int TYPE_IRN_L5 = 1794; // 0x702
-    field @Deprecated public static final int TYPE_IRN_L5CA = 1793; // 0x701
+    field public static final int TYPE_IRN_L5CA = 1793; // 0x701
     field public static final int TYPE_QZS_L1CA = 1025; // 0x401
     field public static final int TYPE_SBS = 513; // 0x201
     field public static final int TYPE_UNKNOWN = 0; // 0x0
diff --git a/location/java/android/location/GnssNavigationMessage.java b/location/java/android/location/GnssNavigationMessage.java
index 5e3f803..7a667ae 100644
--- a/location/java/android/location/GnssNavigationMessage.java
+++ b/location/java/android/location/GnssNavigationMessage.java
@@ -78,9 +78,7 @@
     public static final int TYPE_GAL_F = 0x0602;
     /**
      * NavIC L5 C/A message contained in the structure.
-     * @deprecated deprecated.
      */
-    @Deprecated
     public static final int TYPE_IRN_L5CA = 0x0701;
     /** NavIC L5 message contained in the structure. */
     @FlaggedApi(Flags.FLAG_GNSS_API_NAVIC_L1)
diff --git a/media/java/android/media/session/PlaybackState.java b/media/java/android/media/session/PlaybackState.java
index 60497fe..47637b8 100644
--- a/media/java/android/media/session/PlaybackState.java
+++ b/media/java/android/media/session/PlaybackState.java
@@ -15,7 +15,10 @@
  */
 package android.media.session;
 
+import static com.android.media.flags.Flags.FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE;
+
 import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.LongDef;
 import android.annotation.Nullable;
@@ -189,7 +192,8 @@
      */
     @IntDef({STATE_NONE, STATE_STOPPED, STATE_PAUSED, STATE_PLAYING, STATE_FAST_FORWARDING,
             STATE_REWINDING, STATE_BUFFERING, STATE_ERROR, STATE_CONNECTING,
-            STATE_SKIPPING_TO_PREVIOUS, STATE_SKIPPING_TO_NEXT, STATE_SKIPPING_TO_QUEUE_ITEM})
+            STATE_SKIPPING_TO_PREVIOUS, STATE_SKIPPING_TO_NEXT, STATE_SKIPPING_TO_QUEUE_ITEM,
+            STATE_PLAYBACK_SUPPRESSED})
     @Retention(RetentionPolicy.SOURCE)
     public @interface State {}
 
@@ -286,6 +290,19 @@
     public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11;
 
     /**
+     * State indicating that playback is paused due to an external transient interruption, like a
+     * phone call.
+     *
+     * <p>This state is different from {@link #STATE_PAUSED} in that it is deemed transitory,
+     * possibly allowing the service associated to the session in this state to run in the
+     * foreground.
+     *
+     * @see Builder#setState
+     */
+    @FlaggedApi(FLAG_ENABLE_NOTIFYING_ACTIVITY_MANAGER_WITH_MEDIA_SESSION_STATUS_CHANGE)
+    public static final int STATE_PLAYBACK_SUPPRESSED = 12;
+
+    /**
      * Use this value for the position to indicate the position is not known.
      */
     public static final long PLAYBACK_POSITION_UNKNOWN = -1;
@@ -384,6 +401,7 @@
      * <li> {@link PlaybackState#STATE_SKIPPING_TO_PREVIOUS}</li>
      * <li> {@link PlaybackState#STATE_SKIPPING_TO_NEXT}</li>
      * <li> {@link PlaybackState#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
+     * <li> {@link PlaybackState#STATE_PLAYBACK_SUPPRESSED}</li>
      * </ul>
      */
     @State
@@ -507,6 +525,7 @@
      * <li>{@link #STATE_SKIPPING_TO_NEXT}</li>
      * <li>{@link #STATE_SKIPPING_TO_PREVIOUS}</li>
      * <li>{@link #STATE_SKIPPING_TO_QUEUE_ITEM}</li>
+     * <li>{@link #STATE_PLAYBACK_SUPPRESSED}</li>
      * </ul>
      */
     public boolean isActive() {
@@ -519,33 +538,12 @@
             case PlaybackState.STATE_BUFFERING:
             case PlaybackState.STATE_CONNECTING:
             case PlaybackState.STATE_PLAYING:
+            case PlaybackState.STATE_PLAYBACK_SUPPRESSED:
                 return true;
         }
         return false;
     }
 
-    /**
-     * Returns whether the service holding the media session should run in the foreground when the
-     * media session has this playback state or not.
-     *
-     * @hide
-     */
-    public boolean shouldAllowServiceToRunInForeground() {
-        switch (mState) {
-            case PlaybackState.STATE_PLAYING:
-            case PlaybackState.STATE_FAST_FORWARDING:
-            case PlaybackState.STATE_REWINDING:
-            case PlaybackState.STATE_BUFFERING:
-            case PlaybackState.STATE_CONNECTING:
-            case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
-            case PlaybackState.STATE_SKIPPING_TO_NEXT:
-            case PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM:
-                return true;
-            default:
-                return false;
-        }
-    }
-
     public static final @android.annotation.NonNull Parcelable.Creator<PlaybackState> CREATOR =
             new Parcelable.Creator<PlaybackState>() {
         @Override
@@ -586,6 +584,8 @@
                 return "SKIPPING_TO_NEXT";
             case STATE_SKIPPING_TO_QUEUE_ITEM:
                 return "SKIPPING_TO_QUEUE_ITEM";
+            case STATE_PLAYBACK_SUPPRESSED:
+                return "STATE_PLAYBACK_SUPPRESSED";
             default:
                 return "UNKNOWN";
         }
@@ -823,6 +823,7 @@
          * <li> {@link PlaybackState#STATE_SKIPPING_TO_PREVIOUS}</li>
          * <li> {@link PlaybackState#STATE_SKIPPING_TO_NEXT}</li>
          * <li> {@link PlaybackState#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
+         * <li> {@link PlaybackState#STATE_PLAYBACK_SUPPRESSED}</li>
          * </ul>
          *
          * @param state The current state of playback.
@@ -867,6 +868,7 @@
          * <li> {@link PlaybackState#STATE_SKIPPING_TO_PREVIOUS}</li>
          * <li> {@link PlaybackState#STATE_SKIPPING_TO_NEXT}</li>
          * <li> {@link PlaybackState#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
+         * <li> {@link PlaybackState#STATE_PLAYBACK_SUPPRESSED}</li>
          * </ul>
          *
          * @param state The current state of playback.
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index 326e533..aeabbd5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -46,6 +46,7 @@
 import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_DONE
 import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_INTERNAL_ERROR
 import com.android.packageinstaller.v2.model.InstallAborted.Companion.ABORT_REASON_POLICY
+import com.android.packageinstaller.v2.model.InstallAborted.Companion.DLG_NONE
 import com.android.packageinstaller.v2.model.InstallAborted.Companion.DLG_PACKAGE_ERROR
 import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_ANONYMOUS_SOURCE
 import com.android.packageinstaller.v2.model.InstallUserActionRequired.Companion.USER_ACTION_REASON_INSTALL_CONFIRMATION
@@ -283,14 +284,15 @@
                             createSessionParams(intent, pfd, uri.toString())
                         stagedSessionId = packageInstaller.createSession(params)
                     }
-                } catch (e: IOException) {
+                } catch (e: Exception) {
                     Log.w(LOG_TAG, "Failed to create a staging session", e)
                     _stagingResult.value = InstallAborted(
                         ABORT_REASON_INTERNAL_ERROR,
                         resultIntent = Intent().putExtra(
                             Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_APK
                         ),
-                        activityResultCode = Activity.RESULT_FIRST_USER
+                        activityResultCode = Activity.RESULT_FIRST_USER,
+                        errorDialogType =  if (e is IOException) DLG_PACKAGE_ERROR else DLG_NONE
                     )
                     return
                 }
@@ -313,6 +315,14 @@
                     )
                 }
             }
+        } else {
+            _stagingResult.value = InstallAborted(
+                ABORT_REASON_INTERNAL_ERROR,
+                resultIntent = Intent().putExtra(
+                    Intent.EXTRA_INSTALL_RESULT, PackageManager.INSTALL_FAILED_INVALID_URI
+                ),
+                activityResultCode = Activity.RESULT_FIRST_USER
+            )
         }
     }
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
index be49b39..bbb9bca 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallStages.kt
@@ -122,13 +122,14 @@
      */
     val resultIntent: Intent? = null,
     val activityResultCode: Int = Activity.RESULT_CANCELED,
-    val errorDialogType: Int? = 0,
+    val errorDialogType: Int? = DLG_NONE,
 ) : InstallStage(STAGE_ABORTED) {
 
     companion object {
         const val ABORT_REASON_INTERNAL_ERROR = 0
         const val ABORT_REASON_POLICY = 1
         const val ABORT_REASON_DONE = 2
+        const val DLG_NONE = 0
         const val DLG_PACKAGE_ERROR = 1
     }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt
index c109fc6..1d4d178 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallActionListener.kt
@@ -34,6 +34,8 @@
      */
     fun onNegativeResponse(stageCode: Int)
 
+    fun onNegativeResponse(resultCode: Int, data: Intent?)
+
     /**
      * Launch the intent to open the newly installed / updated app.
      */
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
index 2b610d7..6f8eca3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/InstallLaunch.kt
@@ -36,10 +36,10 @@
 import androidx.fragment.app.FragmentManager
 import androidx.lifecycle.ViewModelProvider
 import com.android.packageinstaller.R
-import com.android.packageinstaller.v2.model.InstallRepository
 import com.android.packageinstaller.v2.model.InstallAborted
 import com.android.packageinstaller.v2.model.InstallFailed
 import com.android.packageinstaller.v2.model.InstallInstalling
+import com.android.packageinstaller.v2.model.InstallRepository
 import com.android.packageinstaller.v2.model.InstallStage
 import com.android.packageinstaller.v2.model.InstallSuccess
 import com.android.packageinstaller.v2.model.InstallUserActionRequired
@@ -50,6 +50,7 @@
 import com.android.packageinstaller.v2.ui.fragments.InstallInstallingFragment
 import com.android.packageinstaller.v2.ui.fragments.InstallStagingFragment
 import com.android.packageinstaller.v2.ui.fragments.InstallSuccessFragment
+import com.android.packageinstaller.v2.ui.fragments.ParseErrorFragment
 import com.android.packageinstaller.v2.ui.fragments.SimpleErrorFragment
 import com.android.packageinstaller.v2.viewmodel.InstallViewModel
 import com.android.packageinstaller.v2.viewmodel.InstallViewModelFactory
@@ -124,8 +125,15 @@
             InstallStage.STAGE_ABORTED -> {
                 val aborted = installStage as InstallAborted
                 when (aborted.abortReason) {
-                    InstallAborted.ABORT_REASON_DONE, InstallAborted.ABORT_REASON_INTERNAL_ERROR ->
-                        setResult(aborted.activityResultCode, aborted.resultIntent, true)
+                    InstallAborted.ABORT_REASON_DONE,
+                    InstallAborted.ABORT_REASON_INTERNAL_ERROR -> {
+                        if (aborted.errorDialogType == InstallAborted.DLG_PACKAGE_ERROR) {
+                            val parseErrorDialog = ParseErrorFragment(aborted)
+                            showDialogInner(parseErrorDialog)
+                        } else {
+                            setResult(aborted.activityResultCode, aborted.resultIntent, true)
+                        }
+                    }
 
                     InstallAborted.ABORT_REASON_POLICY -> showPolicyRestrictionDialog(aborted)
                     else -> setResult(Activity.RESULT_CANCELED, null, true)
@@ -204,7 +212,7 @@
             val blockedByPolicyDialog = createDevicePolicyRestrictionDialog(restriction)
             // Don't finish the package installer app since the next dialog
             // will be shown by this app
-            shouldFinish = blockedByPolicyDialog != null
+            shouldFinish = blockedByPolicyDialog == null
             showDialogInner(blockedByPolicyDialog)
         }
         setResult(Activity.RESULT_CANCELED, null, shouldFinish)
@@ -267,6 +275,10 @@
         setResult(Activity.RESULT_CANCELED, null, true)
     }
 
+    override fun onNegativeResponse(resultCode: Int, data: Intent?) {
+        setResult(resultCode, data, true)
+    }
+
     override fun sendUnknownAppsIntent(sourcePackageName: String) {
         val settingsIntent = Intent()
         settingsIntent.setAction(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ParseErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ParseErrorFragment.java
new file mode 100644
index 0000000..68d48d6
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ParseErrorFragment.java
@@ -0,0 +1,64 @@
+/*
+ * 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.packageinstaller.v2.ui.fragments;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+import com.android.packageinstaller.R;
+import com.android.packageinstaller.v2.model.InstallAborted;
+import com.android.packageinstaller.v2.ui.InstallActionListener;
+
+public class ParseErrorFragment extends DialogFragment {
+
+    private static final String TAG = ParseErrorFragment.class.getSimpleName();
+    private final InstallAborted mDialogData;
+    private InstallActionListener mInstallActionListener;
+
+    public ParseErrorFragment(InstallAborted dialogData) {
+        mDialogData = dialogData;
+    }
+
+    @Override
+    public void onAttach(@NonNull Context context) {
+        super.onAttach(context);
+        mInstallActionListener = (InstallActionListener) context;
+    }
+
+    @NonNull
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        return new AlertDialog.Builder(getActivity())
+            .setMessage(R.string.Parse_error_dlg_text)
+            .setPositiveButton(R.string.ok,
+                (dialog, which) ->
+                    mInstallActionListener.onNegativeResponse(
+                        mDialogData.getActivityResultCode(), mDialogData.getResultIntent()))
+            .create();
+    }
+
+    @Override
+    public void onCancel(DialogInterface dialog) {
+        super.onCancel(dialog);
+        mInstallActionListener.onNegativeResponse(
+            mDialogData.getActivityResultCode(), mDialogData.getResultIntent());
+    }
+}
diff --git a/packages/SettingsLib/LintChecker/Android.bp b/packages/SettingsLib/LintChecker/Android.bp
new file mode 100644
index 0000000..eb489b1
--- /dev/null
+++ b/packages/SettingsLib/LintChecker/Android.bp
@@ -0,0 +1,33 @@
+// 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_library_host {
+    name: "SettingsLibLintChecker",
+    srcs: ["src/**/*.kt"],
+    plugins: ["auto_service_plugin"],
+    libs: [
+        "auto_service_annotations",
+        "lint_api",
+    ],
+    kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/NullabilityAnnotationsDetector.kt b/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/NullabilityAnnotationsDetector.kt
new file mode 100644
index 0000000..1f06261
--- /dev/null
+++ b/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/NullabilityAnnotationsDetector.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.tools.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.LintFix
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.intellij.psi.PsiModifier
+import com.intellij.psi.PsiPrimitiveType
+import com.intellij.psi.PsiType
+import org.jetbrains.uast.UAnnotated
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UMethod
+
+class NullabilityAnnotationsDetector : Detector(), Detector.UastScanner {
+    override fun getApplicableUastTypes(): List<Class<out UElement>> = listOf(UMethod::class.java)
+
+    override fun createUastHandler(context: JavaContext): UElementHandler? {
+        if (!context.isJavaFile()) return null
+
+        return object : UElementHandler() {
+            override fun visitMethod(node: UMethod) {
+                if (node.isPublic() && node.name != ANONYMOUS_CONSTRUCTOR) {
+                    node.verifyMethod()
+                    node.verifyMethodParameters()
+                }
+            }
+
+            private fun UMethod.isPublic() = modifierList.hasModifierProperty(PsiModifier.PUBLIC)
+
+            private fun UMethod.verifyMethod() {
+                if (isConstructor) return
+                if (returnType.isPrimitive()) return
+                checkAnnotation(METHOD_MSG)
+            }
+
+            private fun UMethod.verifyMethodParameters() {
+                for (parameter in uastParameters) {
+                    if (parameter.type.isPrimitive()) continue
+                    parameter.checkAnnotation(PARAMETER_MSG)
+                }
+            }
+
+            private fun PsiType?.isPrimitive() = this is PsiPrimitiveType
+
+            private fun UAnnotated.checkAnnotation(message: String) {
+                val oldAnnotation = findOldNullabilityAnnotation()
+                val oldAnnotationName = oldAnnotation?.qualifiedName?.substringAfterLast('.')
+
+                if (oldAnnotationName != null) {
+                    val annotation = "androidx.annotation.$oldAnnotationName"
+                    reportIssue(
+                        REQUIRE_NULLABILITY_ISSUE,
+                        "Prefer $annotation",
+                        LintFix.create()
+                                .replace()
+                                .range(context.getLocation(oldAnnotation))
+                                .with("@$annotation")
+                                .autoFix()
+                                .build()
+                    )
+                } else if (!hasNullabilityAnnotation()) {
+                    reportIssue(REQUIRE_NULLABILITY_ISSUE, message)
+                }
+            }
+
+            private fun UElement.reportIssue(
+                issue: Issue,
+                message: String,
+                quickfixData: LintFix? = null,
+            ) {
+                context.report(
+                    issue = issue,
+                    scope = this,
+                    location = context.getNameLocation(this),
+                    message = message,
+                    quickfixData = quickfixData,
+                )
+            }
+
+            private fun UAnnotated.findOldNullabilityAnnotation() =
+                uAnnotations.find { it.qualifiedName in oldAnnotations }
+
+            private fun UAnnotated.hasNullabilityAnnotation() =
+                uAnnotations.any { it.qualifiedName in validAnnotations }
+        }
+    }
+
+    private fun JavaContext.isJavaFile() = psiFile?.fileElementType.toString().startsWith("java")
+
+    companion object {
+        private val validAnnotations = arrayOf("androidx.annotation.NonNull",
+            "androidx.annotation.Nullable")
+
+        private val oldAnnotations = arrayOf("android.annotation.NonNull",
+            "android.annotation.Nullable",
+        )
+
+        private const val ANONYMOUS_CONSTRUCTOR = "<anon-init>"
+
+        private const val METHOD_MSG =
+                "Java public method return with non-primitive type must add androidx annotation. " +
+                        "Example: @NonNull | @Nullable Object functionName() {}"
+
+        private const val PARAMETER_MSG =
+                "Java public method parameter with non-primitive type must add androidx " +
+                        "annotation. Example: functionName(@NonNull Context context, " +
+                        "@Nullable Object obj) {}"
+
+        internal val REQUIRE_NULLABILITY_ISSUE = Issue
+            .create(
+                id = "RequiresNullabilityAnnotation",
+                briefDescription = "Requires nullability annotation for function",
+                explanation = "All public java APIs should specify nullability annotations for " +
+                        "methods and parameters.",
+                category = Category.CUSTOM_LINT_CHECKS,
+                priority = 3,
+                severity = Severity.WARNING,
+                androidSpecific = true,
+                implementation = Implementation(
+                  NullabilityAnnotationsDetector::class.java,
+                  Scope.JAVA_FILE_SCOPE,
+                ),
+            )
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/SettingsLintIssueRegistry.kt b/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/SettingsLintIssueRegistry.kt
new file mode 100644
index 0000000..e0ab24a
--- /dev/null
+++ b/packages/SettingsLib/LintChecker/src/com/android/settingslib/tools/lint/SettingsLintIssueRegistry.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.tools.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.auto.service.AutoService
+
+@AutoService(IssueRegistry::class)
+class SettingsLintIssueRegistry : IssueRegistry() {
+    override val issues = listOf(NullabilityAnnotationsDetector.REQUIRE_NULLABILITY_ISSUE)
+
+    override val api: Int = CURRENT_API
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index c143390..b7f2c1e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -34,6 +34,15 @@
         end = itemPaddingEnd,
         bottom = itemPaddingVertical,
     )
+    val textFieldPadding = PaddingValues(
+        start = itemPaddingStart,
+        end = itemPaddingEnd,
+    )
+    val menuFieldPadding = PaddingValues(
+        start = itemPaddingStart,
+        end = itemPaddingEnd,
+        bottom = itemPaddingVertical,
+    )
     val itemPaddingAround = 8.dp
     val itemDividerHeight = 32.dp
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
index 0d6c064..f6692a3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
@@ -51,7 +51,7 @@
         onExpandedChange = { expanded = it },
         modifier = Modifier
             .width(350.dp)
-            .padding(SettingsDimension.itemPadding),
+            .padding(SettingsDimension.menuFieldPadding),
     ) {
         OutlinedTextField(
             // The `menuAnchor` modifier must be passed to the text field for correctness.
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
index 5d248e1..ba8e354 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
@@ -63,7 +63,7 @@
         onExpandedChange = { expanded = it },
         modifier = Modifier
             .width(350.dp)
-            .padding(SettingsDimension.itemPadding)
+            .padding(SettingsDimension.menuFieldPadding)
             .onSizeChanged { dropDownWidth = it.width },
     ) {
         OutlinedTextField(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
index e0dd4e1..2ce3c66 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt
@@ -42,7 +42,7 @@
     OutlinedTextField(
         modifier = Modifier
             .fillMaxWidth()
-            .padding(SettingsDimension.itemPadding),
+            .padding(SettingsDimension.textFieldPadding),
         value = value,
         onValueChange = onTextChange,
         label = {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt
index 0757df3..3102a00 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsTextFieldPassword.kt
@@ -52,7 +52,7 @@
     var visibility by remember { mutableStateOf(false) }
     OutlinedTextField(
         modifier = Modifier
-            .padding(SettingsDimension.itemPadding)
+            .padding(SettingsDimension.menuFieldPadding)
             .fillMaxWidth(),
         value = value,
         onValueChange = onTextChange,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index 2e39adc..add3134 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -93,6 +93,7 @@
         Settings.Global.Wearable.CLOCKWORK_AUTO_TIME,
         Settings.Global.Wearable.CLOCKWORK_AUTO_TIME_ZONE,
         Settings.Global.Wearable.CLOCKWORK_24HR_TIME,
+        Settings.Global.Wearable.CONSISTENT_NOTIFICATION_BLOCKING_ENABLED,
         Settings.Global.Wearable.MUTE_WHEN_OFF_BODY_ENABLED,
         Settings.Global.Wearable.AMBIENT_ENABLED,
         Settings.Global.Wearable.AMBIENT_TILT_TO_WAKE,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 5022395..c0a0760 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -450,6 +450,8 @@
         VALIDATORS.put(Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.CONNECTIVITY_KEEP_DATA_ON, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(
+                Global.Wearable.CONSISTENT_NOTIFICATION_BLOCKING_ENABLED, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.FORCE_ENABLE_PSS_PROFILING, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
index bd99a8b..74fd828 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/WritableNamespacePrefixes.java
@@ -99,7 +99,6 @@
                 "kiwi",
                 "latency_tracker",
                 "launcher",
-                "launcher_lily",
                 "leaked_animator",
                 "lmkd_native",
                 "location",
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 477c42e..507d9c4 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -810,6 +810,9 @@
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE_FILE_MANAGEMENT" />
 
     <!-- Permission required for CTS test - CtsAppFgsTestCases -->
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING" />
+
+    <!-- Permission required for CTS test - CtsAppFgsTestCases -->
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
 
     <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 42107b7..d3a89f4 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -157,7 +157,7 @@
         "SystemUI-res",
         "WifiTrackerLib",
         "WindowManager-Shell",
-        "SystemUIAnimationLib",
+        "PlatformAnimationLib",
         "SystemUICommon",
         "SystemUICustomizationLib",
         "SystemUILogLib",
@@ -274,7 +274,7 @@
     static_libs: [
         "SystemUI-res",
         "WifiTrackerLib",
-        "SystemUIAnimationLib",
+        "PlatformAnimationLib",
         "SystemUIPluginLib",
         "SystemUISharedLib",
         "SystemUICustomizationLib",
diff --git a/packages/SystemUI/aconfig/predictive_back.aconfig b/packages/SystemUI/aconfig/predictive_back.aconfig
index 1ad1666..d0e6b28 100644
--- a/packages/SystemUI/aconfig/predictive_back.aconfig
+++ b/packages/SystemUI/aconfig/predictive_back.aconfig
@@ -19,4 +19,11 @@
     namespace: "systemui"
     description: "Enable Predictive Back Animation in Bouncer"
     bug: "309545085"
+}
+
+flag {
+    name: "predictive_back_animate_dialogs"
+    namespace: "systemui"
+    description: "Enable Predictive Back Animation for SysUI dialogs"
+    bug: "309545085"
 }
\ No newline at end of file
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 8438051..872187a 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -23,7 +23,7 @@
 
 android_library {
 
-    name: "SystemUIAnimationLib",
+    name: "PlatformAnimationLib",
     use_resource_processor: true,
 
     srcs: [
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index b738e2b..efdbfdb 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -494,7 +494,7 @@
             }
 
             for (i in 0 until drawable.numberOfLayers) {
-                (drawable.getDrawable(i) as? GradientDrawable)?.cornerRadii = radii
+                applyBackgroundRadii(drawable.getDrawable(i), radii)
             }
         }
 
diff --git a/packages/SystemUI/compose/core/Android.bp b/packages/SystemUI/compose/core/Android.bp
index 42d088f..9a4347d 100644
--- a/packages/SystemUI/compose/core/Android.bp
+++ b/packages/SystemUI/compose/core/Android.bp
@@ -30,7 +30,7 @@
     ],
 
     static_libs: [
-        "SystemUIAnimationLib",
+        "PlatformAnimationLib",
 
         "androidx.compose.runtime_runtime",
         "androidx.compose.material3_material3",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 8bd5ddb..d201544 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -20,13 +20,13 @@
 import android.os.Bundle
 import android.util.SizeF
 import android.widget.FrameLayout
-import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
@@ -45,6 +45,7 @@
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.material.icons.filled.Edit
 import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.material.icons.outlined.Widgets
 import androidx.compose.material3.Button
 import androidx.compose.material3.ButtonColors
 import androidx.compose.material3.ButtonDefaults
@@ -52,6 +53,7 @@
 import androidx.compose.material3.CardDefaults
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
@@ -72,6 +74,7 @@
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
@@ -100,7 +103,8 @@
     var isDraggingToRemove by remember { mutableStateOf(false) }
 
     Box(
-        modifier = modifier.fillMaxSize().background(Color.White),
+        modifier =
+            modifier.fillMaxSize().background(LocalAndroidColorScheme.current.outlineVariant),
     ) {
         CommunalHubLazyGrid(
             communalContent = communalContent,
@@ -111,7 +115,8 @@
                 isDraggingToRemove =
                     checkForDraggingToRemove(it, removeButtonCoordinates, gridCoordinates)
                 isDraggingToRemove
-            }
+            },
+            onOpenWidgetPicker = onOpenWidgetPicker,
         )
 
         if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
@@ -148,13 +153,14 @@
     contentPadding: PaddingValues,
     setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
     updateDragPositionForRemove: (offset: Offset) -> Boolean,
+    onOpenWidgetPicker: (() -> Unit)? = null,
 ) {
     var gridModifier = Modifier.align(Alignment.CenterStart)
     val gridState = rememberLazyGridState()
     var list = communalContent
     var dragDropState: GridDragDropState? = null
     if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
-        val contentListState = rememberContentListState(communalContent, viewModel)
+        val contentListState = rememberContentListState(list, viewModel)
         list = contentListState.list
         // for drag & drop operations within the communal hub grid
         dragDropState =
@@ -207,17 +213,16 @@
             if (viewModel.isEditMode && dragDropState != null) {
                 DraggableItem(
                     dragDropState = dragDropState,
-                    enabled = true,
+                    enabled = list[index] is CommunalContentModel.Widget,
                     index = index,
                     size = size
-                ) { isDragging ->
-                    val elevation by animateDpAsState(if (isDragging) 4.dp else 1.dp)
+                ) { _ ->
                     CommunalContent(
                         modifier = cardModifier,
-                        elevation = elevation,
                         model = list[index],
                         viewModel = viewModel,
                         size = size,
+                        onOpenWidgetPicker = onOpenWidgetPicker,
                     )
                 }
             } else {
@@ -258,16 +263,11 @@
         horizontalArrangement = Arrangement.SpaceBetween,
         verticalAlignment = Alignment.CenterVertically
     ) {
-        val buttonContentPadding =
-            PaddingValues(
-                vertical = Dimensions.ToolbarButtonPaddingVertical,
-                horizontal = Dimensions.ToolbarButtonPaddingHorizontal,
-            )
         val spacerModifier = Modifier.width(Dimensions.ToolbarButtonSpaceBetween)
         Button(
             onClick = onOpenWidgetPicker,
-            colors = filledSecondaryButtonColors(),
-            contentPadding = buttonContentPadding
+            colors = filledButtonColors(),
+            contentPadding = Dimensions.ButtonPadding
         ) {
             Icon(Icons.Default.Add, stringResource(R.string.button_to_open_widget_editor))
             Spacer(spacerModifier)
@@ -276,25 +276,40 @@
             )
         }
 
-        val buttonColors =
-            if (isDraggingToRemove) filledButtonColors() else ButtonDefaults.outlinedButtonColors()
-        OutlinedButton(
-            onClick = {},
-            colors = buttonColors,
-            contentPadding = buttonContentPadding,
-            modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) },
-        ) {
-            Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_open_widget_editor))
-            Spacer(spacerModifier)
-            Text(
-                text = stringResource(R.string.button_to_remove_widget),
-            )
+        val colors = LocalAndroidColorScheme.current
+        if (isDraggingToRemove) {
+            Button(
+                // Button is disabled to make it non-clickable
+                enabled = false,
+                onClick = {},
+                colors =
+                    ButtonDefaults.buttonColors(
+                        disabledContainerColor = colors.primary,
+                        disabledContentColor = colors.onPrimary,
+                    ),
+                contentPadding = Dimensions.ButtonPadding,
+                modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) }
+            ) {
+                RemoveButtonContent(spacerModifier)
+            }
+        } else {
+            OutlinedButton(
+                // Button is disabled to make it non-clickable
+                enabled = false,
+                onClick = {},
+                colors = ButtonDefaults.outlinedButtonColors(disabledContentColor = colors.primary),
+                border = BorderStroke(width = 1.0.dp, color = colors.primary),
+                contentPadding = Dimensions.ButtonPadding,
+                modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) }
+            ) {
+                RemoveButtonContent(spacerModifier)
+            }
         }
 
         Button(
             onClick = onEditDone,
             colors = filledButtonColors(),
-            contentPadding = buttonContentPadding
+            contentPadding = Dimensions.ButtonPadding
         ) {
             Text(
                 text = stringResource(R.string.hub_mode_editing_exit_button_text),
@@ -304,6 +319,15 @@
 }
 
 @Composable
+private fun RemoveButtonContent(spacerModifier: Modifier) {
+    Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_open_widget_editor))
+    Spacer(spacerModifier)
+    Text(
+        text = stringResource(R.string.button_to_remove_widget),
+    )
+}
+
+@Composable
 private fun filledButtonColors(): ButtonColors {
     val colors = LocalAndroidColorScheme.current
     return ButtonDefaults.buttonColors(
@@ -313,25 +337,20 @@
 }
 
 @Composable
-private fun filledSecondaryButtonColors(): ButtonColors {
-    val colors = LocalAndroidColorScheme.current
-    return ButtonDefaults.buttonColors(
-        containerColor = colors.secondary,
-        contentColor = colors.onSecondary,
-    )
-}
-
-@Composable
 private fun CommunalContent(
     model: CommunalContentModel,
     viewModel: BaseCommunalViewModel,
     size: SizeF,
     modifier: Modifier = Modifier,
-    elevation: Dp = 0.dp,
+    onOpenWidgetPicker: (() -> Unit)? = null,
 ) {
     when (model) {
-        is CommunalContentModel.Widget -> WidgetContent(viewModel, model, size, elevation, modifier)
+        is CommunalContentModel.Widget -> WidgetContent(viewModel, model, size, modifier)
         is CommunalContentModel.WidgetPlaceholder -> WidgetPlaceholderContent(size)
+        is CommunalContentModel.CtaTileInViewMode ->
+            CtaTileInViewModeContent(viewModel, size, modifier)
+        is CommunalContentModel.CtaTileInEditMode ->
+            CtaTileInEditModeContent(size, modifier, onOpenWidgetPicker)
         is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier)
         is CommunalContentModel.Tutorial -> TutorialContent(modifier)
         is CommunalContentModel.Umo -> Umo(viewModel, modifier)
@@ -349,17 +368,125 @@
     ) {}
 }
 
+/** Presents a CTA tile at the end of the grid, to customize the hub. */
+@Composable
+private fun CtaTileInViewModeContent(
+    viewModel: BaseCommunalViewModel,
+    size: SizeF,
+    modifier: Modifier = Modifier,
+) {
+    val colors = LocalAndroidColorScheme.current
+    Card(
+        modifier = modifier.height(size.height.dp),
+        colors =
+            CardDefaults.cardColors(
+                containerColor = colors.primary,
+                contentColor = colors.onPrimary,
+            ),
+        shape = RoundedCornerShape(80.dp, 40.dp, 80.dp, 40.dp)
+    ) {
+        Column(
+            modifier = Modifier.fillMaxSize().padding(horizontal = 82.dp),
+            verticalArrangement =
+                Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically),
+            horizontalAlignment = Alignment.CenterHorizontally,
+        ) {
+            Icon(
+                imageVector = Icons.Outlined.Widgets,
+                contentDescription = stringResource(R.string.cta_label_to_open_widget_picker),
+                modifier = Modifier.size(Dimensions.IconSize),
+            )
+            Text(
+                text = stringResource(R.string.cta_label_to_edit_widget),
+                style = MaterialTheme.typography.titleLarge,
+                textAlign = TextAlign.Center,
+            )
+            Row(
+                modifier = Modifier.fillMaxWidth(),
+                horizontalArrangement = Arrangement.Center,
+            ) {
+                OutlinedButton(
+                    colors =
+                        ButtonDefaults.buttonColors(
+                            contentColor = colors.onPrimary,
+                        ),
+                    border = BorderStroke(width = 1.0.dp, color = colors.primaryContainer),
+                    contentPadding = Dimensions.ButtonPadding,
+                    onClick = viewModel::onDismissCtaTile,
+                ) {
+                    Text(
+                        text = stringResource(R.string.cta_tile_button_to_dismiss),
+                    )
+                }
+                Spacer(modifier = Modifier.size(Dimensions.Spacing))
+                Button(
+                    colors =
+                        ButtonDefaults.buttonColors(
+                            containerColor = colors.primaryContainer,
+                            contentColor = colors.onPrimaryContainer,
+                        ),
+                    contentPadding = Dimensions.ButtonPadding,
+                    onClick = viewModel::onOpenWidgetEditor
+                ) {
+                    Text(
+                        text = stringResource(R.string.cta_tile_button_to_open_widget_editor),
+                    )
+                }
+            }
+        }
+    }
+}
+
+/** Presents a CTA tile at the end of the hub in edit mode, to add more widgets. */
+@Composable
+private fun CtaTileInEditModeContent(
+    size: SizeF,
+    modifier: Modifier = Modifier,
+    onOpenWidgetPicker: (() -> Unit)? = null,
+) {
+    if (onOpenWidgetPicker == null) {
+        throw IllegalArgumentException("onOpenWidgetPicker should not be null.")
+    }
+    val colors = LocalAndroidColorScheme.current
+    Card(
+        modifier = modifier.height(size.height.dp),
+        colors = CardDefaults.cardColors(containerColor = Color.Transparent),
+        border = BorderStroke(1.dp, colors.primary),
+        shape = RoundedCornerShape(200.dp),
+        onClick = onOpenWidgetPicker,
+    ) {
+        Column(
+            modifier = Modifier.fillMaxSize(),
+            verticalArrangement =
+                Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically),
+            horizontalAlignment = Alignment.CenterHorizontally,
+        ) {
+            Icon(
+                imageVector = Icons.Outlined.Widgets,
+                contentDescription = stringResource(R.string.cta_label_to_open_widget_picker),
+                tint = colors.primary,
+                modifier = Modifier.size(Dimensions.IconSize),
+            )
+            Text(
+                text = stringResource(R.string.cta_label_to_open_widget_picker),
+                style = MaterialTheme.typography.titleLarge,
+                color = colors.primary,
+                textAlign = TextAlign.Center,
+            )
+        }
+    }
+}
+
 @Composable
 private fun WidgetContent(
     viewModel: BaseCommunalViewModel,
     model: CommunalContentModel.Widget,
     size: SizeF,
-    elevation: Dp,
     modifier: Modifier = Modifier,
 ) {
-    Card(
+    Box(
         modifier = modifier.height(size.height.dp),
-        elevation = CardDefaults.cardElevation(draggedElevation = elevation),
+        contentAlignment = Alignment.Center,
     ) {
         AndroidView(
             modifier = modifier,
@@ -502,4 +629,10 @@
     val ToolbarButtonPaddingHorizontal = 24.dp
     val ToolbarButtonPaddingVertical = 16.dp
     val ToolbarButtonSpaceBetween = 8.dp
+    val ButtonPadding =
+        PaddingValues(
+            vertical = ToolbarButtonPaddingVertical,
+            horizontal = ToolbarButtonPaddingHorizontal,
+        )
+    val IconSize = 48.dp
 }
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 54c5de7..35a5054 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
@@ -487,7 +487,7 @@
                     // page should be composed.
                     HorizontalPager(
                         pagerState,
-                        beyondBoundsPageCount = 0,
+                        outOfBoundsPageCount = 0,
                     ) { page ->
                         when (page) {
                             0 -> Box(Modifier.element(TestElements.Foo).fillMaxSize())
diff --git a/packages/SystemUI/customization/Android.bp b/packages/SystemUI/customization/Android.bp
index 927fd8e..1d18496 100644
--- a/packages/SystemUI/customization/Android.bp
+++ b/packages/SystemUI/customization/Android.bp
@@ -30,8 +30,8 @@
         "src/**/*.aidl",
     ],
     static_libs: [
+        "PlatformAnimationLib",
         "PluginCoreLib",
-        "SystemUIAnimationLib",
         "SystemUIPluginLib",
         "SystemUIUnfoldLib",
         "androidx.dynamicanimation_dynamicanimation",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 36aa441..cec2d74 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -496,7 +496,8 @@
         final float[] scaleFactor = new float[]{1f, displayHeight[1] / (float) displayHeight[0]};
         final int[] rotation = new int[]{Surface.ROTATION_0, Surface.ROTATION_90};
         final UdfpsOverlayParams oldParams = new UdfpsOverlayParams(sensorBounds[0],
-                sensorBounds[0], displayWidth[0], displayHeight[0], scaleFactor[0], rotation[0]);
+                sensorBounds[0], displayWidth[0], displayHeight[0], scaleFactor[0], rotation[0],
+                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
 
         for (int i1 = 0; i1 <= 1; ++i1) {
             for (int i2 = 0; i2 <= 1; ++i2) {
@@ -505,7 +506,8 @@
                         for (int i5 = 0; i5 <= 1; ++i5) {
                             final UdfpsOverlayParams newParams = new UdfpsOverlayParams(
                                     sensorBounds[i1], sensorBounds[i1], displayWidth[i2],
-                                    displayHeight[i3], scaleFactor[i4], rotation[i5]);
+                                    displayHeight[i3], scaleFactor[i4], rotation[i5],
+                                    FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
 
                             if (newParams.equals(oldParams)) {
                                 continue;
@@ -549,7 +551,7 @@
         // Initialize the overlay.
         mUdfpsController.updateOverlayParams(mOpticalProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
-                        scaleFactor, rotation));
+                        scaleFactor, rotation, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
 
         // Show the overlay.
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
@@ -560,7 +562,7 @@
         // Update overlay with the same parameters.
         mUdfpsController.updateOverlayParams(mOpticalProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
-                        scaleFactor, rotation));
+                        scaleFactor, rotation, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
         mFgExecutor.runAllReady();
 
         // Ensure the overlay was not recreated.
@@ -642,7 +644,8 @@
         // Test ROTATION_0
         mUdfpsController.updateOverlayParams(testParams.sensorProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
-                        scaleFactor, Surface.ROTATION_0));
+                        scaleFactor, Surface.ROTATION_0,
+                        FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
         MotionEvent event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor,
                 touchMajor);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
@@ -657,7 +660,8 @@
         reset(mFingerprintManager);
         mUdfpsController.updateOverlayParams(testParams.sensorProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
-                        scaleFactor, Surface.ROTATION_90));
+                        scaleFactor, Surface.ROTATION_90,
+                        FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
         event = obtainMotionEvent(ACTION_DOWN, displayHeight, 0, touchMinor, touchMajor);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricExecutor.runAllReady();
@@ -671,7 +675,8 @@
         reset(mFingerprintManager);
         mUdfpsController.updateOverlayParams(testParams.sensorProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
-                        scaleFactor, Surface.ROTATION_270));
+                        scaleFactor, Surface.ROTATION_270,
+                        FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
         event = obtainMotionEvent(ACTION_DOWN, 0, displayWidth, touchMinor, touchMajor);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
         mBiometricExecutor.runAllReady();
@@ -685,7 +690,8 @@
         reset(mFingerprintManager);
         mUdfpsController.updateOverlayParams(testParams.sensorProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
-                        scaleFactor, Surface.ROTATION_180));
+                        scaleFactor, Surface.ROTATION_180,
+                        FingerprintSensorProperties.TYPE_UDFPS_OPTICAL));
         // ROTATION_180 is not supported. It should be treated like ROTATION_0.
         event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor, touchMajor);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
similarity index 69%
rename from packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
index 455f986..92b75cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalMediaRepositoryImplTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -25,13 +25,13 @@
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlin.test.Test
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.verify
@@ -59,37 +59,43 @@
     }
 
     @Test
-    fun mediaPlaying_defaultsToFalse() =
+    fun hasAnyMediaOrRecommendation_defaultsToFalse() =
         testScope.runTest {
             mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
 
-            val isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+            val mediaModel = collectLastValue(mediaRepository.mediaModel)
             runCurrent()
-            assertThat(isMediaPlaying()).isFalse()
+            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
         }
 
     @Test
-    fun mediaPlaying_emitsInitialValue() =
-        testScope.runTest {
-            // Start with media available.
-            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
-
-            mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
-            val isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
-            runCurrent()
-            assertThat(isMediaPlaying()).isTrue()
-        }
-
-    @Test
-    fun mediaPlaying_updatesWhenMediaDataLoaded() =
+    fun mediaModel_updatesWhenMediaDataLoaded() =
         testScope.runTest {
             mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
 
+            // Listener is added
+            verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
+
             // Initial value is false.
-            var isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+            val mediaModel = collectLastValue(mediaRepository.mediaModel)
             runCurrent()
-            assertThat(isMediaPlaying()).isFalse()
+            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
+
+            // Change to media available and notify the listener.
+            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
+            whenever(mediaData.createdTimestampMillis).thenReturn(1234L)
+            mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
+            runCurrent()
+
+            // Media active now returns true.
+            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
+            assertThat(mediaModel()?.createdTimestampMillis).isEqualTo(1234L)
+        }
+
+    @Test
+    fun mediaModel_updatesWhenMediaDataRemoved() =
+        testScope.runTest {
+            mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
 
             // Listener is added
             verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
@@ -97,36 +103,18 @@
             // Change to media available and notify the listener.
             whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
             mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
-
-            // mediaPlaying now returns true.
-            isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
             runCurrent()
-            assertThat(isMediaPlaying()).isTrue()
-        }
 
-    @Test
-    fun mediaPlaying_updatesWhenMediaDataRemoved() =
-        testScope.runTest {
-            // Start with media available.
-            whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(true)
-
-            mediaRepository = CommunalMediaRepositoryImpl(mediaDataManager)
-
-            // Initial value is true.
-            var isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
-            runCurrent()
-            assertThat(isMediaPlaying()).isTrue()
-
-            // Listener is added.
-            verify(mediaDataManager).addListener(mediaDataListenerCaptor.capture())
+            // Media active now returns true.
+            val mediaModel = collectLastValue(mediaRepository.mediaModel)
+            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isTrue()
 
             // Change to media unavailable and notify the listener.
             whenever(mediaDataManager.hasAnyMediaOrRecommendation()).thenReturn(false)
-            mediaDataListenerCaptor.value.onMediaDataLoaded("key", null, mediaData)
-
-            // mediaPlaying now returns false.
-            isMediaPlaying = collectLastValue(mediaRepository.mediaPlaying)
+            mediaDataListenerCaptor.value.onMediaDataRemoved("key")
             runCurrent()
-            assertThat(isMediaPlaying()).isFalse()
+
+            // Media active now returns false.
+            assertThat(mediaModel()?.hasAnyMediaOrRecommendation).isFalse()
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 62084aa..744b65f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -148,25 +148,29 @@
             whenever(target1.smartspaceTargetId).thenReturn("target1")
             whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_WEATHER)
             whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java))
+            whenever(target1.creationTimeMillis).thenReturn(0L)
 
             // Does not have RemoteViews
             val target2 = mock(SmartspaceTarget::class.java)
-            whenever(target1.smartspaceTargetId).thenReturn("target2")
-            whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
-            whenever(target1.remoteViews).thenReturn(null)
+            whenever(target2.smartspaceTargetId).thenReturn("target2")
+            whenever(target2.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+            whenever(target2.remoteViews).thenReturn(null)
+            whenever(target2.creationTimeMillis).thenReturn(0L)
 
             // Timer and has RemoteViews
             val target3 = mock(SmartspaceTarget::class.java)
-            whenever(target1.smartspaceTargetId).thenReturn("target3")
-            whenever(target1.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
-            whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java))
+            whenever(target3.smartspaceTargetId).thenReturn("target3")
+            whenever(target3.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+            whenever(target3.remoteViews).thenReturn(mock(RemoteViews::class.java))
+            whenever(target3.creationTimeMillis).thenReturn(0L)
 
             val targets = listOf(target1, target2, target3)
             smartspaceRepository.setCommunalSmartspaceTargets(targets)
 
-            val smartspaceContent by collectLastValue(underTest.smartspaceContent)
+            val smartspaceContent by collectLastValue(underTest.ongoingContent)
             assertThat(smartspaceContent?.size).isEqualTo(1)
-            assertThat(smartspaceContent?.get(0)?.key).isEqualTo("smartspace_target3")
+            assertThat(smartspaceContent?.get(0)?.key)
+                .isEqualTo(CommunalContentModel.KEY.smartspace("target3"))
         }
 
     @Test
@@ -256,16 +260,12 @@
 
             val targets = mutableListOf<SmartspaceTarget>()
             for (index in 0 until totalTargets) {
-                val target = mock(SmartspaceTarget::class.java)
-                whenever(target.smartspaceTargetId).thenReturn("target$index")
-                whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
-                whenever(target.remoteViews).thenReturn(mock(RemoteViews::class.java))
-                targets.add(target)
+                targets.add(smartspaceTimer(index.toString()))
             }
 
             smartspaceRepository.setCommunalSmartspaceTargets(targets)
 
-            val smartspaceContent by collectLastValue(underTest.smartspaceContent)
+            val smartspaceContent by collectLastValue(underTest.ongoingContent)
             assertThat(smartspaceContent?.size).isEqualTo(totalTargets)
             for (index in 0 until totalTargets) {
                 assertThat(smartspaceContent?.get(index)?.size).isEqualTo(expectedSizes[index])
@@ -279,13 +279,77 @@
             tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
             // Media is playing.
-            mediaRepository.mediaPlaying.value = true
+            mediaRepository.mediaActive()
 
-            val umoContent by collectLastValue(underTest.umoContent)
+            val umoContent by collectLastValue(underTest.ongoingContent)
 
             assertThat(umoContent?.size).isEqualTo(1)
             assertThat(umoContent?.get(0)).isInstanceOf(CommunalContentModel.Umo::class.java)
-            assertThat(umoContent?.get(0)?.key).isEqualTo(CommunalContentModel.UMO_KEY)
+            assertThat(umoContent?.get(0)?.key).isEqualTo(CommunalContentModel.KEY.umo())
+        }
+
+    @Test
+    fun ongoing_shouldOrderAndSizeByTimestamp() =
+        testScope.runTest {
+            // Keyguard showing, and tutorial completed.
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+            // Timer1 started
+            val timer1 = smartspaceTimer("timer1", timestamp = 1L)
+            smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1))
+
+            // Umo started
+            mediaRepository.mediaActive(timestamp = 2L)
+
+            // Timer2 started
+            val timer2 = smartspaceTimer("timer2", timestamp = 3L)
+            smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1, timer2))
+
+            // Timer3 started
+            val timer3 = smartspaceTimer("timer3", timestamp = 4L)
+            smartspaceRepository.setCommunalSmartspaceTargets(listOf(timer1, timer2, timer3))
+
+            val ongoingContent by collectLastValue(underTest.ongoingContent)
+            assertThat(ongoingContent?.size).isEqualTo(4)
+            assertThat(ongoingContent?.get(0)?.key)
+                .isEqualTo(CommunalContentModel.KEY.smartspace("timer3"))
+            assertThat(ongoingContent?.get(0)?.size).isEqualTo(CommunalContentSize.FULL)
+            assertThat(ongoingContent?.get(1)?.key)
+                .isEqualTo(CommunalContentModel.KEY.smartspace("timer2"))
+            assertThat(ongoingContent?.get(1)?.size).isEqualTo(CommunalContentSize.THIRD)
+            assertThat(ongoingContent?.get(2)?.key).isEqualTo(CommunalContentModel.KEY.umo())
+            assertThat(ongoingContent?.get(2)?.size).isEqualTo(CommunalContentSize.THIRD)
+            assertThat(ongoingContent?.get(3)?.key)
+                .isEqualTo(CommunalContentModel.KEY.smartspace("timer1"))
+            assertThat(ongoingContent?.get(3)?.size).isEqualTo(CommunalContentSize.THIRD)
+        }
+
+    @Test
+    fun cta_visibilityTrue_shows() =
+        testScope.runTest {
+            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+            communalRepository.setCtaTileInViewModeVisibility(true)
+
+            val ctaTileContent by collectLastValue(underTest.ctaTileContent)
+
+            assertThat(ctaTileContent?.size).isEqualTo(1)
+            assertThat(ctaTileContent?.get(0))
+                .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
+            assertThat(ctaTileContent?.get(0)?.key)
+                .isEqualTo(CommunalContentModel.KEY.CTA_TILE_IN_VIEW_MODE_KEY)
+        }
+
+    @Test
+    fun ctaTile_visibilityFalse_doesNotShow() =
+        testScope.runTest {
+            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+            communalRepository.setCtaTileInViewModeVisibility(false)
+
+            val ctaTileContent by collectLastValue(underTest.ctaTileContent)
+
+            assertThat(ctaTileContent).isEmpty()
         }
 
     @Test
@@ -334,4 +398,13 @@
             underTest.showWidgetEditor()
             verify(editWidgetsActivityStarter).startActivity()
         }
+
+    private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget {
+        val timer = mock(SmartspaceTarget::class.java)
+        whenever(timer.smartspaceTargetId).thenReturn(id)
+        whenever(timer.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+        whenever(timer.remoteViews).thenReturn(mock(RemoteViews::class.java))
+        whenever(timer.creationTimeMillis).thenReturn(timestamp)
+        return timer
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index f2f9705..4a935d0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -91,7 +91,7 @@
     }
 
     @Test
-    fun communalContent_onlyWidgetsAreShownInEditMode() =
+    fun communalContent_onlyWidgetsAndCtaTileAreShownInEditMode() =
         testScope.runTest {
             tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
 
@@ -119,16 +119,18 @@
             smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
 
             // Media playing.
-            mediaRepository.mediaPlaying.value = true
+            mediaRepository.mediaActive()
 
             val communalContent by collectLastValue(underTest.communalContent)
 
-            // Only Widgets are shown.
-            assertThat(communalContent?.size).isEqualTo(2)
+            // Only Widgets and CTA tile are shown.
+            assertThat(communalContent?.size).isEqualTo(3)
             assertThat(communalContent?.get(0))
                 .isInstanceOf(CommunalContentModel.Widget::class.java)
             assertThat(communalContent?.get(1))
                 .isInstanceOf(CommunalContentModel.Widget::class.java)
+            assertThat(communalContent?.get(2))
+                .isInstanceOf(CommunalContentModel.CtaTileInEditMode::class.java)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 182cc5d..16e0bc0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -112,7 +112,7 @@
         }
 
     @Test
-    fun ordering_smartspaceBeforeUmoBeforeWidgets() =
+    fun ordering_smartspaceBeforeUmoBeforeWidgetsBeforeCtaTile() =
         testScope.runTest {
             tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
 
@@ -140,12 +140,15 @@
             smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
 
             // Media playing.
-            mediaRepository.mediaPlaying.value = true
+            mediaRepository.mediaActive()
+
+            // CTA Tile not dismissed.
+            communalRepository.setCtaTileInViewModeVisibility(true)
 
             val communalContent by collectLastValue(underTest.communalContent)
 
-            // Order is smart space, then UMO, then widget content.
-            assertThat(communalContent?.size).isEqualTo(4)
+            // Order is smart space, then UMO, widget content and cta tile.
+            assertThat(communalContent?.size).isEqualTo(5)
             assertThat(communalContent?.get(0))
                 .isInstanceOf(CommunalContentModel.Smartspace::class.java)
             assertThat(communalContent?.get(1)).isInstanceOf(CommunalContentModel.Umo::class.java)
@@ -153,5 +156,7 @@
                 .isInstanceOf(CommunalContentModel.Widget::class.java)
             assertThat(communalContent?.get(3))
                 .isInstanceOf(CommunalContentModel.Widget::class.java)
+            assertThat(communalContent?.get(4))
+                .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 6c4bb37..c4ebbdc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -24,6 +24,7 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.doze.DozeMachine
@@ -71,6 +72,7 @@
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
     private lateinit var systemClock: FakeSystemClock
+    private lateinit var facePropertyRepository: FakeFacePropertyRepository
 
     private lateinit var underTest: KeyguardRepositoryImpl
 
@@ -78,6 +80,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         systemClock = FakeSystemClock()
+        facePropertyRepository = FakeFacePropertyRepository()
         underTest =
             KeyguardRepositoryImpl(
                 statusBarStateController,
@@ -89,6 +92,7 @@
                 mainDispatcher,
                 testScope.backgroundScope,
                 systemClock,
+                facePropertyRepository,
             )
     }
 
@@ -482,10 +486,7 @@
         testScope.runTest {
             val values = mutableListOf<Point?>()
             val job = underTest.faceSensorLocation.onEach(values::add).launchIn(this)
-
-            val captor = argumentCaptor<AuthController.Callback>()
             runCurrent()
-            verify(authController).addCallback(captor.capture())
 
             // An initial, null value should be initially emitted so that flows combined with this
             // one
@@ -500,8 +501,7 @@
                     Point(250, 250),
                 )
                 .onEach {
-                    whenever(authController.faceSensorLocation).thenReturn(it)
-                    captor.value.onFaceSensorLocationChanged()
+                    facePropertyRepository.setSensorLocation(it)
                     runCurrent()
                 }
                 .also { dispatchedSensorLocations ->
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
index 00405d0..c2ce392 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
@@ -29,27 +29,37 @@
 import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.util.time.FakeSystemClock
 import java.time.Instant
 import java.time.LocalDateTime
 import java.util.TimeZone
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class AlarmTileMapperTest : SysuiTestCase() {
+    private val oneMinute = 60000L
     private val kosmos = Kosmos()
     private val alarmTileConfig = kosmos.qsAlarmTileConfig
+    private val fakeClock = FakeSystemClock()
     // Using lazy (versus =) to make sure we override the right context -- see b/311612168
     private val mapper by lazy {
         AlarmTileMapper(
             context.orCreateTestableResources
                 .apply { addOverride(R.drawable.ic_alarm, TestStubDrawable()) }
                 .resources,
-            context.theme
+            context.theme,
+            fakeClock
         )
     }
 
+    @Before
+    fun setup() {
+        fakeClock.setCurrentTimeMillis(0) // same time both in test & map()
+    }
+
     @Test
     fun notAlarmSet() {
         val inputModel = AlarmTileModel.NoAlarmSet
@@ -66,7 +76,7 @@
 
     @Test
     fun nextAlarmSet24HourFormat() {
-        val triggerTime = 1L
+        val triggerTime = fakeClock.currentTimeMillis() + oneMinute
         val inputModel =
             AlarmTileModel.NextAlarmSet(true, AlarmManager.AlarmClockInfo(triggerTime, null))
 
@@ -85,7 +95,7 @@
 
     @Test
     fun nextAlarmSet12HourFormat() {
-        val triggerTime = 1L
+        val triggerTime = fakeClock.currentTimeMillis() + oneMinute
         val inputModel =
             AlarmTileModel.NextAlarmSet(false, AlarmManager.AlarmClockInfo(triggerTime, null))
 
@@ -102,6 +112,66 @@
         QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
     }
 
+    @Test
+    fun nextAlarmSetMoreThanAWeekLater_mapsSecondaryLabelToDisplayDateOnly() {
+        val oneWeekAndOneMinute = 7 * 24 * 60 * 60 * 1000L + oneMinute
+        val triggerTime = fakeClock.currentTimeMillis() + oneWeekAndOneMinute
+        val inputModel =
+            AlarmTileModel.NextAlarmSet(false, AlarmManager.AlarmClockInfo(triggerTime, null))
+
+        val outputState = mapper.map(alarmTileConfig, inputModel)
+
+        val localDateTime =
+            LocalDateTime.ofInstant(
+                Instant.ofEpochMilli(triggerTime),
+                TimeZone.getDefault().toZoneId()
+            )
+        val expectedSecondaryLabel = AlarmTileMapper.formatterDateOnly.format(localDateTime)
+        val expectedState =
+            createAlarmTileState(QSTileState.ActivationState.ACTIVE, expectedSecondaryLabel)
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun nextAlarmSetOneMinuteLessThanAWeekLater_mapsSecondaryLabelToDisplayTime() {
+        val oneWeekMinusOneMinute = 7 * 24 * 60 * 60 * 1000L - oneMinute
+        val triggerTime = fakeClock.currentTimeMillis() + oneWeekMinusOneMinute
+        val inputModel =
+            AlarmTileModel.NextAlarmSet(false, AlarmManager.AlarmClockInfo(triggerTime, null))
+
+        val outputState = mapper.map(alarmTileConfig, inputModel)
+
+        val localDateTime =
+            LocalDateTime.ofInstant(
+                Instant.ofEpochMilli(triggerTime),
+                TimeZone.getDefault().toZoneId()
+            )
+        val expectedSecondaryLabel = AlarmTileMapper.formatter12Hour.format(localDateTime)
+        val expectedState =
+            createAlarmTileState(QSTileState.ActivationState.ACTIVE, expectedSecondaryLabel)
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun nextAlarmSetExactlyAWeekLater_mapsSecondaryLabelToDisplayDateOnly() {
+        val oneWeek = 7 * 24 * 60 * 60 * 1000L
+        val triggerTime = fakeClock.currentTimeMillis() + oneWeek
+        val inputModel =
+            AlarmTileModel.NextAlarmSet(false, AlarmManager.AlarmClockInfo(triggerTime, null))
+
+        val outputState = mapper.map(alarmTileConfig, inputModel)
+
+        val localDateTime =
+            LocalDateTime.ofInstant(
+                Instant.ofEpochMilli(triggerTime),
+                TimeZone.getDefault().toZoneId()
+            )
+        val expectedSecondaryLabel = AlarmTileMapper.formatterDateOnly.format(localDateTime)
+        val expectedState =
+            createAlarmTileState(QSTileState.ActivationState.ACTIVE, expectedSecondaryLabel)
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
     private fun createAlarmTileState(
         activationState: QSTileState.ActivationState,
         secondaryLabel: String
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
index de767e3..7274c0c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
@@ -21,29 +21,30 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 import android.content.BroadcastReceiver;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Configuration;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.Dependency;
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.model.SysUiState;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -61,17 +62,17 @@
 public class SystemUIDialogTest extends SysuiTestCase {
 
     @Mock
-    private FeatureFlags mFeatureFlags;
-    @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
     private SystemUIDialog.Delegate mDelegate;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
         mDependency.injectTestDependency(BroadcastDispatcher.class, mBroadcastDispatcher);
     }
 
@@ -110,16 +111,13 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_DIALOGS)
     public void usePredictiveBackAnimFlag() {
-        when(mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM))
-                .thenReturn(true);
         final SystemUIDialog dialog = new SystemUIDialog(mContext);
 
         dialog.show();
 
         assertTrue(dialog.isShowing());
-        verify(mFeatureFlags, atLeast(1))
-                .isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM);
 
         dialog.dismiss();
         assertFalse(dialog.isShowing());
@@ -174,7 +172,6 @@
     private SystemUIDialog createDialogWithDelegate() {
         SystemUIDialog.Factory factory = new SystemUIDialog.Factory(
                 getContext(),
-                mFeatureFlags,
                 Dependency.get(SystemUIDialogManager.class),
                 Dependency.get(SysUiState.class),
                 Dependency.get(BroadcastDispatcher.class),
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index 0537f17..9063a02 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -46,8 +46,8 @@
     static_libs: [
         "androidx.annotation_annotation",
         "androidx-constraintlayout_constraintlayout",
+        "PlatformAnimationLib",
         "PluginCoreLib",
-        "SystemUIAnimationLib",
         "SystemUICommon",
         "SystemUILogLib",
         "androidx.annotation_annotation",
diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml
index f4b0a45..84681d34 100644
--- a/packages/SystemUI/res/layout/remote_input.xml
+++ b/packages/SystemUI/res/layout/remote_input.xml
@@ -89,7 +89,6 @@
                 android:textColorHint="@color/remote_input_hint"
                 android:textSize="16sp"
                 android:background="@null"
-                android:maxLines="4"
                 android:ellipsize="start"
                 android:inputType="textShortMessage|textMultiLine|textAutoCorrect|textCapSentences"
                 android:imeOptions="actionSend|flagNoExtractUi|flagNoFullscreen" />
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 10f7c4d..64a1d24 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -538,15 +538,15 @@
        -->
     <string translatable="false" name="config_frontBuiltInDisplayCutoutProtection"></string>
 
-    <!--  ID for the camera of outer display that needs extra protection -->
+    <!-- ID for the camera of outer display that needs extra protection -->
     <string translatable="false" name="config_protectedCameraId"></string>
-    <!--  Physical ID for the camera of outer display that needs extra protection -->
+    <!-- Physical ID for the camera of outer display that needs extra protection -->
     <string translatable="false" name="config_protectedPhysicalCameraId"></string>
 
     <!-- Similar to config_frontBuiltInDisplayCutoutProtection but for inner display. -->
     <string translatable="false" name="config_innerBuiltInDisplayCutoutProtection"></string>
 
-    <!-- ID for the camera of inner display that needs extra protection -->
+    <!-- ID for the camera of inner display that needs extra protection. -->
     <string translatable="false" name="config_protectedInnerCameraId"></string>
     <!-- Physical ID for the camera of inner display that needs extra protection -->
     <string translatable="false" name="config_protectedInnerPhysicalCameraId"></string>
@@ -650,13 +650,20 @@
     <!-- Whether to use window background blur for the volume dialog. -->
     <bool name="config_volumeDialogUseBackgroundBlur">false</bool>
 
-    <!-- The properties of the face auth camera in pixels -->
+    <!-- The properties of the face auth front camera for outer display in pixels -->
     <integer-array name="config_face_auth_props">
         <!-- sensorLocationX -->
         <!-- sensorLocationY -->
         <!--sensorRadius -->
     </integer-array>
 
+    <!-- The properties of the face auth front camera for inner display in pixels -->
+    <integer-array name="config_inner_face_auth_props">
+        <!-- sensorLocationX -->
+        <!-- sensorLocationY -->
+        <!--sensorRadius -->
+    </integer-array>
+
     <!-- Overrides the behavior of the face unlock keyguard bypass setting:
          0 - Don't override the setting (default)
          1 - Override the setting to always bypass keyguard
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 854bb0f..3f11fae 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1079,6 +1079,14 @@
 
     <!-- Description for the button that opens the widget editor on click. [CHAR LIMIT=50] -->
     <string name="button_to_open_widget_editor">Open the widget editor</string>
+    <!-- Text for CTA button that launches the hub mode widget editor on click. [CHAR LIMIT=50] -->
+    <string name="cta_tile_button_to_open_widget_editor">Customize</string>
+    <!-- Text for CTA button that dismisses the tile on click. [CHAR LIMIT=50] -->
+    <string name="cta_tile_button_to_dismiss">Dismiss</string>
+    <!-- Label for CTA tile to edit the glanceable hub [CHAR LIMIT=100] -->
+    <string name="cta_label_to_edit_widget">Add, remove, and reorder your widgets in this space</string>
+    <!-- Label for CTA tile that opens widget picker on click in edit mode [CHAR LIMIT=50] -->
+    <string name="cta_label_to_open_widget_picker">Add more widgets</string>
     <!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] -->
     <string name="button_to_remove_widget">Remove</string>
     <!-- Text for the button that launches the hub mode widget picker. [CHAR LIMIT=50] -->
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 3a26ebf..05106c9 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -51,8 +51,8 @@
     ],
     static_libs: [
         "BiometricsSharedLib",
+        "PlatformAnimationLib",
         "PluginCoreLib",
-        "SystemUIAnimationLib",
         "SystemUIPluginLib",
         "SystemUIUnfoldLib",
         "SystemUISharedLib-Keyguard",
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
index 65c5a49..e31fb89 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
@@ -17,6 +17,8 @@
 package com.android.systemui.biometrics.shared.model
 
 import android.graphics.Rect
+import android.hardware.fingerprint.FingerprintSensorProperties.SensorType
+import android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
 import android.view.Surface
 import android.view.Surface.Rotation
 
@@ -39,6 +41,8 @@
  * the native resolution.
  *
  * [rotation] current rotation of the display.
+ *
+ * [sensorType] fingerprint sensor type
  */
 data class UdfpsOverlayParams(
     val sensorBounds: Rect = Rect(),
@@ -46,7 +50,8 @@
     val naturalDisplayWidth: Int = 0,
     val naturalDisplayHeight: Int = 0,
     val scaleFactor: Float = 1f,
-    @Rotation val rotation: Int = Surface.ROTATION_0
+    @Rotation val rotation: Int = Surface.ROTATION_0,
+    @SensorType val sensorType: Int = TYPE_UDFPS_OPTICAL
 ) {
 
     /** Same as [sensorBounds], but in native resolution. */
diff --git a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/KeyButtonRipple.java
similarity index 98%
rename from packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
rename to packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/KeyButtonRipple.java
index f005af3..92473e8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/navigationbar/KeyButtonRipple.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.buttons;
+package com.android.systemui.shared.navigationbar;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -125,7 +125,7 @@
     /**
      *  @param onInvisibleRunnable run after we are next drawn invisibly. Only used once.
      */
-    void setOnInvisibleRunnable(Runnable onInvisibleRunnable) {
+    public void setOnInvisibleRunnable(Runnable onInvisibleRunnable) {
         mOnInvisibleRunnable = onInvisibleRunnable;
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java
index a4b6451..2145166 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java
@@ -30,7 +30,7 @@
 
 import androidx.annotation.DimenRes;
 
-import com.android.systemui.navigationbar.buttons.KeyButtonRipple;
+import com.android.systemui.shared.navigationbar.KeyButtonRipple;
 
 public class FloatingRotationButtonView extends ImageView {
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/statusbar/policy/CallbackController.java b/packages/SystemUI/shared/src/com/android/systemui/statusbar/policy/CallbackController.java
index 047ff75..9f82201 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/statusbar/policy/CallbackController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/statusbar/policy/CallbackController.java
@@ -21,6 +21,11 @@
 import androidx.lifecycle.LifecycleEventObserver;
 import androidx.lifecycle.LifecycleOwner;
 
+/**
+ * Implementation of the collection used and thread guarantees are left to the discretion of the
+ * client. Consider using {@link com.android.systemui.util.ListenerSet} to prevent concurrent
+ * modification exceptions.
+ */
 public interface CallbackController<T> {
 
     /** Add a callback */
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index e03c627..d6d5c26 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -68,7 +68,7 @@
 
 import com.android.internal.util.Preconditions;
 import com.android.settingslib.Utils;
-import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.decor.CutoutDecorProviderFactory;
 import com.android.systemui.decor.DebugRoundedCornerDelegate;
@@ -92,6 +92,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.ThreadFactory;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.SecureSettings;
 
 import dalvik.annotation.optimization.NeverCompile;
@@ -131,8 +132,6 @@
     };
     private final ScreenDecorationsLogger mLogger;
 
-    private final AuthController mAuthController;
-
     private DisplayTracker mDisplayTracker;
     @VisibleForTesting
     protected boolean mIsRegistered;
@@ -183,6 +182,9 @@
     private DisplayCutout mDisplayCutout;
     private boolean mPendingManualConfigUpdate;
 
+    private FacePropertyRepository mFacePropertyRepository;
+    private JavaAdapter mJavaAdapter;
+
     @VisibleForTesting
     protected void showCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
         if (mFaceScanningFactory.shouldShowFaceScanningAnim()) {
@@ -330,7 +332,8 @@
             PrivacyDotDecorProviderFactory dotFactory,
             FaceScanningProviderFactory faceScanningFactory,
             ScreenDecorationsLogger logger,
-            AuthController authController) {
+            FacePropertyRepository facePropertyRepository,
+            JavaAdapter javaAdapter) {
         mContext = context;
         mSecureSettings = secureSettings;
         mCommandRegistry = commandRegistry;
@@ -342,22 +345,10 @@
         mFaceScanningFactory = faceScanningFactory;
         mFaceScanningViewId = com.android.systemui.res.R.id.face_scanning_anim;
         mLogger = logger;
-        mAuthController = authController;
+        mFacePropertyRepository = facePropertyRepository;
+        mJavaAdapter = javaAdapter;
     }
 
-
-    private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
-        @Override
-        public void onFaceSensorLocationChanged() {
-            mLogger.onSensorLocationChanged();
-            if (mExecutor != null) {
-                mExecutor.execute(
-                        () -> updateOverlayProviderViews(
-                                new Integer[]{mFaceScanningViewId}));
-            }
-        }
-    };
-
     private final ScreenDecorCommand.Callback mScreenDecorCommandCallback = (cmd, pw) -> {
         // If we are exiting debug mode, we can set it (false) and bail, otherwise we will
         // ensure that debug mode is set
@@ -407,7 +398,8 @@
         mExecutor = mThreadFactory.buildDelayableExecutorOnHandler(mHandler);
         mExecutor.execute(this::startOnScreenDecorationsThread);
         mDotViewController.setUiExecutor(mExecutor);
-        mAuthController.addCallback(mAuthControllerCallback);
+        mJavaAdapter.alwaysCollectFlow(mFacePropertyRepository.getSensorLocation(),
+                this::onFaceSensorLocationChanged);
         mCommandRegistry.registerCommand(ScreenDecorCommand.SCREEN_DECOR_CMD_NAME,
                 () -> new ScreenDecorCommand(mScreenDecorCommandCallback));
     }
@@ -1320,6 +1312,16 @@
         view.setLayoutParams(params);
     }
 
+    @VisibleForTesting
+    void onFaceSensorLocationChanged(Point location) {
+        mLogger.onSensorLocationChanged();
+        if (mExecutor != null) {
+            mExecutor.execute(
+                    () -> updateOverlayProviderViews(
+                            new Integer[]{mFaceScanningViewId}));
+        }
+    }
+
     public static class DisplayCutoutView extends DisplayCutoutBaseView {
         final List<Rect> mBounds = new ArrayList();
         final Rect mBoundingRect = new Rect();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index a4f90eb..093a1ff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -148,10 +148,6 @@
 
     private final Display mDisplay;
     private float mScaleFactor = 1f;
-    // sensor locations without any resolution scaling nor rotation adjustments:
-    @Nullable private final Point mFaceSensorLocationDefault;
-    // cached sensor locations:
-    @Nullable private Point mFaceSensorLocation;
     @Nullable private Point mFingerprintSensorLocation;
     @Nullable private Rect mUdfpsBounds;
     private final Set<Callback> mCallbacks = new HashSet<>();
@@ -622,7 +618,6 @@
         mScaleFactor = mUdfpsUtils.getScaleFactor(mCachedDisplayInfo);
         updateUdfpsLocation();
         updateFingerprintLocation();
-        updateFaceLocation();
     }
     /**
      * @return where the fingerprint sensor exists in pixels in its natural orientation.
@@ -682,31 +677,6 @@
     }
 
     /**
-     * @return where the face sensor exists in pixels in the current device orientation. Returns
-     * null if no face sensor exists.
-     */
-    @Nullable public Point getFaceSensorLocation() {
-        return mFaceSensorLocation;
-    }
-
-    private void updateFaceLocation() {
-        if (mFaceProps == null || mFaceSensorLocationDefault == null) {
-            mFaceSensorLocation = null;
-        } else {
-            mFaceSensorLocation = rotateToCurrentOrientation(
-                    new Point(
-                            (int) (mFaceSensorLocationDefault.x * mScaleFactor),
-                            (int) (mFaceSensorLocationDefault.y * mScaleFactor)),
-                    mCachedDisplayInfo
-            );
-        }
-
-        for (final Callback cb : mCallbacks) {
-            cb.onFaceSensorLocationChanged();
-        }
-    }
-
-    /**
      * @param inOutPoint point on the display in pixels. Going in, represents the point
      *                   in the device's natural orientation. Going out, represents
      *                   the point in the display's current orientation.
@@ -821,17 +791,7 @@
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mPanelInteractionDetector = panelInteractionDetector;
 
-
         mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
-        int[] faceAuthLocation = context.getResources().getIntArray(
-                com.android.systemui.res.R.array.config_face_auth_props);
-        if (faceAuthLocation == null || faceAuthLocation.length < 2) {
-            mFaceSensorLocationDefault = null;
-        } else {
-            mFaceSensorLocationDefault = new Point(
-                    faceAuthLocation[0],
-                    faceAuthLocation[1]);
-        }
 
         mDisplay = mContext.getDisplay();
         updateSensorLocations();
@@ -868,7 +828,8 @@
                     mCachedDisplayInfo.getNaturalWidth(),
                     mCachedDisplayInfo.getNaturalHeight(),
                     mScaleFactor,
-                    mCachedDisplayInfo.rotation);
+                    mCachedDisplayInfo.rotation,
+                    udfpsProp.sensorType);
 
             mUdfpsController.updateOverlayParams(udfpsProp, mUdfpsOverlayParams);
             if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds) || !Objects.equals(
@@ -1358,8 +1319,6 @@
         final AuthDialog dialog = mCurrentDialog;
         pw.println("  mCachedDisplayInfo=" + mCachedDisplayInfo);
         pw.println("  mScaleFactor=" + mScaleFactor);
-        pw.println("  faceAuthSensorLocationDefault=" + mFaceSensorLocationDefault);
-        pw.println("  faceAuthSensorLocation=" + getFaceSensorLocation());
         pw.println("  fingerprintSensorLocationInNaturalOrientation="
                 + getFingerprintSensorLocationInNaturalOrientation());
         pw.println("  fingerprintSensorLocation=" + getFingerprintSensorLocation());
@@ -1433,11 +1392,5 @@
          * {@link #onFingerprintLocationChanged}.
          */
         default void onUdfpsLocationChanged(UdfpsOverlayParams udfpsOverlayParams) {}
-
-        /**
-         * Called when the location of the face unlock sensor (typically the front facing camera)
-         * changes. The location in pixels can change due to resolution changes.
-         */
-        default void onFaceSensorLocationChanged() {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 45967c6..86f372a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -32,6 +32,7 @@
 import com.android.settingslib.Utils
 import com.android.systemui.CoreStartable
 import com.android.systemui.Flags.lightRevealMigration
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
@@ -80,6 +81,7 @@
     private val logger: KeyguardLogger,
     private val biometricUnlockController: BiometricUnlockController,
     private val lightRevealScrim: LightRevealScrim,
+    private val facePropertyRepository: FacePropertyRepository,
     rippleView: AuthRippleView?
 ) :
     ViewController<AuthRippleView>(rippleView),
@@ -263,7 +265,7 @@
 
     fun updateSensorLocation() {
         fingerprintSensorLocation = authController.fingerprintSensorLocation
-        faceSensorLocation = authController.faceSensorLocation
+        faceSensorLocation = facePropertyRepository.sensorLocation.value
     }
 
     private fun updateRippleColor() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index b0143f5..aaccbc1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -22,6 +22,7 @@
 import android.hardware.display.DisplayManager.DisplayListener
 import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
 import android.os.Handler
+import android.util.Size
 import android.view.DisplayInfo
 import com.android.internal.util.ArrayUtils
 import com.android.systemui.biometrics.shared.model.DisplayRotation
@@ -40,6 +41,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
 /** Repository for the current state of the display */
@@ -58,6 +60,9 @@
 
     /** Provides the current display rotation */
     val currentRotation: StateFlow<DisplayRotation>
+
+    /** Provides the current display size */
+    val currentDisplaySize: StateFlow<Size>
 }
 
 // TODO(b/296211844): This class could directly use DeviceStateRepository and DisplayRepository
@@ -110,17 +115,13 @@
                 initialValue = false,
             )
 
-    private fun getDisplayRotation(): DisplayRotation {
+    private fun getDisplayInfo(): DisplayInfo {
         val cachedDisplayInfo = DisplayInfo()
         context.display?.getDisplayInfo(cachedDisplayInfo)
-        var rotation = cachedDisplayInfo.rotation
-        if (isReverseDefaultRotation) {
-            rotation = (rotation + 1) % 4
-        }
-        return rotation.toDisplayRotation()
+        return cachedDisplayInfo
     }
 
-    override val currentRotation: StateFlow<DisplayRotation> =
+    private val currentDisplayInfo: StateFlow<DisplayInfo> =
         conflatedCallbackFlow {
                 val callback =
                     object : DisplayListener {
@@ -129,11 +130,11 @@
                         override fun onDisplayAdded(displayId: Int) {}
 
                         override fun onDisplayChanged(displayId: Int) {
-                            val rotation = getDisplayRotation()
+                            val displayInfo = getDisplayInfo()
                             trySendWithFailureLogging(
-                                rotation,
+                                displayInfo,
                                 TAG,
-                                "Error sending display rotation to $rotation"
+                                "Error sending displayInfo to $displayInfo"
                             )
                         }
                     }
@@ -148,7 +149,37 @@
             .stateIn(
                 applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue = getDisplayRotation(),
+                initialValue = getDisplayInfo(),
+            )
+
+    private fun rotationToDisplayRotation(rotation: Int): DisplayRotation {
+        var adjustedRotation = rotation
+        if (isReverseDefaultRotation) {
+            adjustedRotation = (rotation + 1) % 4
+        }
+        return adjustedRotation.toDisplayRotation()
+    }
+
+    override val currentRotation: StateFlow<DisplayRotation> =
+        currentDisplayInfo
+            .map { rotationToDisplayRotation(it.rotation) }
+            .stateIn(
+                applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = rotationToDisplayRotation(currentDisplayInfo.value.rotation)
+            )
+
+    override val currentDisplaySize: StateFlow<Size> =
+        currentDisplayInfo
+            .map { Size(it.naturalWidth, it.naturalHeight) }
+            .stateIn(
+                applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue =
+                    Size(
+                        currentDisplayInfo.value.naturalWidth,
+                        currentDisplayInfo.value.naturalHeight
+                    ),
             )
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
index 0ae2e16..ae1539e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FacePropertyRepository.kt
@@ -17,25 +17,39 @@
 
 package com.android.systemui.biometrics.data.repository
 
+import android.content.Context
+import android.graphics.Point
+import android.hardware.camera2.CameraManager
 import android.hardware.face.FaceManager
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.hardware.face.IFaceAuthenticatorsRegisteredCallback
 import android.util.Log
+import android.util.RotationUtils
+import android.util.Size
+import com.android.systemui.biometrics.shared.model.DisplayRotation
 import com.android.systemui.biometrics.shared.model.LockoutMode
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.biometrics.shared.model.toLockoutMode
+import com.android.systemui.biometrics.shared.model.toRotation
 import com.android.systemui.biometrics.shared.model.toSensorStrength
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
@@ -47,20 +61,38 @@
 
     /** Get the current lockout mode for the user. This makes a binder based service call. */
     suspend fun getLockoutMode(userId: Int): LockoutMode
+
+    /** The current face sensor location in current device rotation */
+    val sensorLocation: StateFlow<Point?>
 }
 
 /** Describes a biometric sensor */
 data class FaceSensorInfo(val id: Int, val strength: SensorStrength)
 
+/** Data class for camera info */
+private data class CameraInfo(
+    /** The logical id of the camera */
+    val cameraId: String,
+    /** The physical id of the camera */
+    val cameraPhysicalId: String?,
+    /** The center point of the camera in natural orientation */
+    val cameraLocation: Point?,
+)
+
 private const val TAG = "FaceSensorPropertyRepositoryImpl"
 
 @SysUISingleton
 class FacePropertyRepositoryImpl
 @Inject
 constructor(
+    @Application val applicationContext: Context,
+    @Main mainExecutor: Executor,
     @Application private val applicationScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val faceManager: FaceManager?,
+    private val cameraManager: CameraManager,
+    displayStateRepository: DisplayStateRepository,
+    configurationRepository: ConfigurationRepository,
 ) : FacePropertyRepository {
 
     override val sensorInfo: StateFlow<FaceSensorInfo?> =
@@ -89,10 +121,179 @@
             .onEach { Log.d(TAG, "sensorProps changed: $it") }
             .stateIn(applicationScope, SharingStarted.Eagerly, null)
 
+    private val cameraInfoList: List<CameraInfo> = loadCameraInfoList()
+    private var currentPhysicalCameraId: String? = null
+
+    private val defaultSensorLocation: StateFlow<Point?> =
+        ConflatedCallbackFlow.conflatedCallbackFlow {
+                val callback =
+                    object : CameraManager.AvailabilityCallback() {
+
+                        // This callback will only be called when there is more than one front
+                        // camera on the device (e.g. foldable device with cameras on both outer &
+                        // inner display).
+                        override fun onPhysicalCameraAvailable(
+                            cameraId: String,
+                            physicalCameraId: String
+                        ) {
+                            currentPhysicalCameraId = physicalCameraId
+                            val cameraInfo =
+                                cameraInfoList.firstOrNull {
+                                    physicalCameraId == it.cameraPhysicalId
+                                }
+                            trySendWithFailureLogging(
+                                cameraInfo?.cameraLocation,
+                                TAG,
+                                "Update face sensor location to $cameraInfo."
+                            )
+                        }
+
+                        // This callback will only be called when there is more than one front
+                        // camera on the device (e.g. foldable device with cameras on both outer &
+                        // inner display).
+                        //
+                        // By default, all cameras are available which means there will be no
+                        // onPhysicalCameraAvailable() invoked and depending on the device state
+                        // (Fold or unfold), only the onPhysicalCameraUnavailable() for another
+                        // camera will be invoke. So we need to use this method to decide the
+                        // initial physical ID for foldable devices.
+                        override fun onPhysicalCameraUnavailable(
+                            cameraId: String,
+                            physicalCameraId: String
+                        ) {
+                            if (currentPhysicalCameraId == null) {
+                                val cameraInfo =
+                                    cameraInfoList.firstOrNull {
+                                        physicalCameraId != it.cameraPhysicalId
+                                    }
+                                currentPhysicalCameraId = cameraInfo?.cameraPhysicalId
+                                trySendWithFailureLogging(
+                                    cameraInfo?.cameraLocation,
+                                    TAG,
+                                    "Update face sensor location to $cameraInfo."
+                                )
+                            }
+                        }
+                    }
+                cameraManager.registerAvailabilityCallback(mainExecutor, callback)
+                awaitClose { cameraManager.unregisterAvailabilityCallback(callback) }
+            }
+            .stateIn(
+                applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue =
+                    if (cameraInfoList.isNotEmpty()) cameraInfoList[0].cameraLocation else null
+            )
+
+    override val sensorLocation: StateFlow<Point?> =
+        sensorInfo
+            .flatMapLatest { info ->
+                if (info == null) {
+                    flowOf(null)
+                } else {
+                    combine(
+                        defaultSensorLocation,
+                        displayStateRepository.currentRotation,
+                        displayStateRepository.currentDisplaySize,
+                        configurationRepository.scaleForResolution
+                    ) { defaultLocation, displayRotation, displaySize, scaleForResolution ->
+                        computeCurrentFaceLocation(
+                            defaultLocation,
+                            displayRotation,
+                            displaySize,
+                            scaleForResolution
+                        )
+                    }
+                }
+            }
+            .stateIn(
+                applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = null
+            )
+
+    private fun computeCurrentFaceLocation(
+        defaultLocation: Point?,
+        rotation: DisplayRotation,
+        displaySize: Size,
+        scaleForResolution: Float,
+    ): Point? {
+        if (defaultLocation == null) {
+            return null
+        }
+
+        return rotateToCurrentOrientation(
+            Point(
+                (defaultLocation.x * scaleForResolution).toInt(),
+                (defaultLocation.y * scaleForResolution).toInt()
+            ),
+            rotation,
+            displaySize
+        )
+    }
+
+    private fun rotateToCurrentOrientation(
+        inOutPoint: Point,
+        rotation: DisplayRotation,
+        displaySize: Size
+    ): Point {
+        RotationUtils.rotatePoint(
+            inOutPoint,
+            rotation.toRotation(),
+            displaySize.width,
+            displaySize.height
+        )
+        return inOutPoint
+    }
     override suspend fun getLockoutMode(userId: Int): LockoutMode {
         if (sensorInfo.value == null || faceManager == null) {
             return LockoutMode.NONE
         }
         return faceManager.getLockoutModeForUser(sensorInfo.value!!.id, userId).toLockoutMode()
     }
+
+    private fun loadCameraInfoList(): List<CameraInfo> {
+        val list = mutableListOf<CameraInfo>()
+
+        val outer =
+            loadCameraInfo(
+                R.string.config_protectedCameraId,
+                R.string.config_protectedPhysicalCameraId,
+                R.array.config_face_auth_props
+            )
+        if (outer != null) {
+            list.add(outer)
+        }
+
+        val inner =
+            loadCameraInfo(
+                R.string.config_protectedInnerCameraId,
+                R.string.config_protectedInnerPhysicalCameraId,
+                R.array.config_inner_face_auth_props
+            )
+        if (inner != null) {
+            list.add(inner)
+        }
+        return list
+    }
+
+    private fun loadCameraInfo(
+        cameraIdRes: Int,
+        cameraPhysicalIdRes: Int,
+        cameraLocationRes: Int
+    ): CameraInfo? {
+        val cameraId = applicationContext.getString(cameraIdRes)
+        if (cameraId.isNullOrEmpty()) {
+            return null
+        }
+        val physicalCameraId = applicationContext.getString(cameraPhysicalIdRes)
+        val cameraLocation: IntArray = applicationContext.resources.getIntArray(cameraLocationRes)
+        val location: Point?
+        if (cameraLocation.size < 2) {
+            location = null
+        } else {
+            location = Point(cameraLocation[0], cameraLocation[1])
+        }
+        return CameraInfo(cameraId, physicalCameraId, location)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
new file mode 100644
index 0000000..cf2e33c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalMediaModel.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.model
+
+/** Data model of media on the communal hub. */
+data class CommunalMediaModel(
+    val hasAnyMediaOrRecommendation: Boolean,
+    val createdTimestampMillis: Long = 0L,
+) {
+    companion object {
+        val INACTIVE =
+            CommunalMediaModel(
+                hasAnyMediaOrRecommendation = false,
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
index e41c322..e8a561b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalMediaRepository.kt
@@ -16,18 +16,17 @@
 
 package com.android.systemui.communal.data.repository
 
+import com.android.systemui.communal.data.model.CommunalMediaModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.media.controls.models.player.MediaData
 import com.android.systemui.media.controls.pipeline.MediaDataManager
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.flow.onStart
 
 /** Encapsulates the state of smartspace in communal. */
 interface CommunalMediaRepository {
-    val mediaPlaying: Flow<Boolean>
+    val mediaModel: Flow<CommunalMediaModel>
 }
 
 @SysUISingleton
@@ -47,27 +46,32 @@
                 receivedSmartspaceCardLatency: Int,
                 isSsReactivated: Boolean
             ) {
-                if (!mediaDataManager.hasAnyMediaOrRecommendation()) {
-                    return
-                }
-                _mediaPlaying.value = true
+                updateMediaModel(data)
             }
 
             override fun onMediaDataRemoved(key: String) {
-                if (mediaDataManager.hasAnyMediaOrRecommendation()) {
-                    return
-                }
-                _mediaPlaying.value = false
+                updateMediaModel()
             }
         }
 
-    private val _mediaPlaying: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    init {
+        mediaDataManager.addListener(mediaDataListener)
+    }
 
-    override val mediaPlaying: Flow<Boolean> =
-        _mediaPlaying
-            .onStart {
-                mediaDataManager.addListener(mediaDataListener)
-                _mediaPlaying.value = mediaDataManager.hasAnyMediaOrRecommendation()
-            }
-            .onCompletion { mediaDataManager.removeListener(mediaDataListener) }
+    private val _mediaModel: MutableStateFlow<CommunalMediaModel> =
+        MutableStateFlow(CommunalMediaModel.INACTIVE)
+
+    override val mediaModel: Flow<CommunalMediaModel> = _mediaModel
+
+    private fun updateMediaModel(data: MediaData? = null) {
+        if (mediaDataManager.hasAnyMediaOrRecommendation()) {
+            _mediaModel.value =
+                CommunalMediaModel(
+                    hasAnyMediaOrRecommendation = true,
+                    createdTimestampMillis = data?.createdTimestampMillis ?: 0L,
+                )
+        } else {
+            _mediaModel.value = CommunalMediaModel.INACTIVE
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 1f4be40..553b3eb 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -56,6 +56,9 @@
     /** Exposes the transition state of the communal [SceneTransitionLayout]. */
     val transitionState: StateFlow<ObservableCommunalTransitionState>
 
+    /** Whether the CTA tile is visible in the hub under view mode. */
+    val isCtaTileInViewModeVisible: Flow<Boolean>
+
     /** Updates the requested scene. */
     fun setDesiredScene(desiredScene: CommunalSceneKey)
 
@@ -65,6 +68,9 @@
      * Note that you must call is with `null` when the UI is done or risk a memory leak.
      */
     fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?)
+
+    /** Updates whether to display the CTA tile in the hub under view mode. */
+    fun setCtaTileInViewModeVisibility(isVisible: Boolean)
 }
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -96,6 +102,16 @@
                 initialValue = defaultTransitionState,
             )
 
+    // TODO(b/313462210) - persist the value in local storage, so the tile won't show up again
+    //  once dismissed.
+    private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
+    override val isCtaTileInViewModeVisible: Flow<Boolean> =
+        _isCtaTileInViewModeVisible.asStateFlow()
+
+    override fun setCtaTileInViewModeVisibility(isVisible: Boolean) {
+        _isCtaTileInViewModeVisible.value = isVisible
+    }
+
     override fun setDesiredScene(desiredScene: CommunalSceneKey) {
         _desiredScene.value = desiredScene
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 18fb895..8271238 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -34,15 +34,13 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.smartspace.data.repository.SmartspaceRepository
 import javax.inject.Inject
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 
 /** Encapsulates business-logic related to communal mode. */
-@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class CommunalInteractor
 @Inject
@@ -98,6 +96,9 @@
         editWidgetsActivityStarter.startActivity()
     }
 
+    /** Dismiss the CTA tile from the hub in view mode. */
+    fun dismissCtaTile() = communalRepository.setCtaTileInViewModeVisibility(isVisible = false)
+
     /** Add a widget at the specified position. */
     fun addWidget(componentName: ComponentName, priority: Int) =
         widgetRepository.addWidget(componentName, priority)
@@ -125,27 +126,25 @@
             }
         }
 
-    /** A flow of available smartspace content. Currently only showing timer targets. */
-    val smartspaceContent: Flow<List<CommunalContentModel.Smartspace>> =
+    /** A flow of available smartspace targets. Currently only showing timers. */
+    private val smartspaceTargets: Flow<List<SmartspaceTarget>> =
         if (!smartspaceRepository.isSmartspaceRemoteViewsEnabled) {
             flowOf(emptyList())
         } else {
             smartspaceRepository.communalSmartspaceTargets.map { targets ->
-                targets
-                    .filter { target ->
-                        target.featureType == SmartspaceTarget.FEATURE_TIMER &&
-                            target.remoteViews != null
-                    }
-                    .mapIndexed Target@{ index, target ->
-                        return@Target CommunalContentModel.Smartspace(
-                            smartspaceTargetId = target.smartspaceTargetId,
-                            remoteViews = target.remoteViews!!,
-                            size = dynamicContentSize(targets.size, index),
-                        )
-                    }
+                targets.filter { target ->
+                    target.featureType == SmartspaceTarget.FEATURE_TIMER &&
+                        target.remoteViews != null
+                }
             }
         }
 
+    /** CTA tile to be displayed in the glanceable hub (view mode). */
+    val ctaTileContent: Flow<List<CommunalContentModel.CtaTileInViewMode>> =
+        communalRepository.isCtaTileInViewModeVisible.map { visible ->
+            if (visible) listOf(CommunalContentModel.CtaTileInViewMode()) else emptyList()
+        }
+
     /** A list of tutorial content to be displayed in the communal hub in tutorial mode. */
     val tutorialContent: List<CommunalContentModel.Tutorial> =
         listOf(
@@ -159,14 +158,43 @@
             CommunalContentModel.Tutorial(id = 7, HALF),
         )
 
-    val umoContent: Flow<List<CommunalContentModel.Umo>> =
-        mediaRepository.mediaPlaying.flatMapLatest { mediaPlaying ->
-            if (mediaPlaying) {
-                // TODO(b/310254801): support HALF and FULL layouts
-                flowOf(listOf(CommunalContentModel.Umo(THIRD)))
-            } else {
-                flowOf(emptyList())
+    /**
+     * A flow of ongoing content, including smartspace timers and umo, ordered by creation time and
+     * sized dynamically.
+     */
+    val ongoingContent: Flow<List<CommunalContentModel.Ongoing>> =
+        combine(smartspaceTargets, mediaRepository.mediaModel) { smartspace, media ->
+            val ongoingContent = mutableListOf<CommunalContentModel.Ongoing>()
+
+            // Add smartspace
+            ongoingContent.addAll(
+                smartspace.map { target ->
+                    CommunalContentModel.Smartspace(
+                        smartspaceTargetId = target.smartspaceTargetId,
+                        remoteViews = target.remoteViews!!,
+                        createdTimestampMillis = target.creationTimeMillis,
+                    )
+                }
+            )
+
+            // Add UMO
+            if (media.hasAnyMediaOrRecommendation) {
+                ongoingContent.add(
+                    CommunalContentModel.Umo(
+                        createdTimestampMillis = media.createdTimestampMillis,
+                    )
+                )
             }
+
+            // Order by creation time descending
+            ongoingContent.sortByDescending { it.createdTimestampMillis }
+
+            // Dynamic sizing
+            ongoingContent.forEachIndexed { index, model ->
+                model.size = dynamicContentSize(ongoingContent.size, index)
+            }
+
+            return@combine ongoingContent
         }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 3ae5229..46f957f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -30,46 +30,95 @@
     /** Size to be rendered in the grid. */
     val size: CommunalContentSize
 
+    /**
+     * A type of communal content is ongoing / live / ephemeral, and can be sized and ordered
+     * dynamically.
+     */
+    sealed interface Ongoing : CommunalContentModel {
+        override var size: CommunalContentSize
+
+        /** Timestamp in milliseconds of when the content was created. */
+        val createdTimestampMillis: Long
+    }
+
     class Widget(
         val appWidgetId: Int,
         val providerInfo: AppWidgetProviderInfo,
         val appWidgetHost: AppWidgetHost,
     ) : CommunalContentModel {
-        override val key = "widget_$appWidgetId"
+        override val key = KEY.widget(appWidgetId)
         // Widget size is always half.
         override val size = CommunalContentSize.HALF
     }
 
     /** A placeholder item representing a new widget being added */
     class WidgetPlaceholder : CommunalContentModel {
-        override val key: String = "widget_placeholder_${UUID.randomUUID()}"
+        override val key: String = KEY.widgetPlaceholder()
+        // Same as widget size.
+        override val size = CommunalContentSize.HALF
+    }
+
+    /** A CTA tile in the glanceable hub view mode which can be dismissed. */
+    class CtaTileInViewMode : CommunalContentModel {
+        override val key: String = KEY.CTA_TILE_IN_VIEW_MODE_KEY
+        // Same as widget size.
+        override val size = CommunalContentSize.HALF
+    }
+
+    /** A CTA tile in the glanceable hub edit model which remains visible in the grid. */
+    class CtaTileInEditMode : CommunalContentModel {
+        override val key: String = KEY.CTA_TILE_IN_EDIT_MODE_KEY
         // Same as widget size.
         override val size = CommunalContentSize.HALF
     }
 
     class Tutorial(
         id: Int,
-        override val size: CommunalContentSize,
+        override var size: CommunalContentSize,
     ) : CommunalContentModel {
-        override val key = "tutorial_$id"
+        override val key = KEY.tutorial(id)
     }
 
     class Smartspace(
         smartspaceTargetId: String,
         val remoteViews: RemoteViews,
-        override val size: CommunalContentSize,
-    ) : CommunalContentModel {
-        override val key = "smartspace_$smartspaceTargetId"
+        override val createdTimestampMillis: Long,
+        override var size: CommunalContentSize = CommunalContentSize.HALF,
+    ) : Ongoing {
+        override val key = KEY.smartspace(smartspaceTargetId)
     }
 
     class Umo(
-        override val size: CommunalContentSize,
-    ) : CommunalContentModel {
-        override val key = UMO_KEY
+        override val createdTimestampMillis: Long,
+        override var size: CommunalContentSize = CommunalContentSize.HALF,
+    ) : Ongoing {
+        override val key = KEY.umo()
     }
 
-    companion object {
-        /** Key for the [Umo] in CommunalContentModel. There should only ever be one UMO. */
-        const val UMO_KEY = "umo"
+    class KEY {
+        companion object {
+            const val CTA_TILE_IN_VIEW_MODE_KEY = "cta_tile_in_view_mode"
+            const val CTA_TILE_IN_EDIT_MODE_KEY = "cta_tile_in_edit_mode"
+
+            fun widget(id: Int): String {
+                return "widget_$id"
+            }
+
+            fun widgetPlaceholder(): String {
+                return "widget_placeholder_${UUID.randomUUID()}"
+            }
+
+            fun tutorial(id: Int): String {
+                return "tutorial_$id"
+            }
+
+            fun smartspace(id: String): String {
+                return "smartspace_$id"
+            }
+
+            fun umo(): String {
+                return "umo"
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
new file mode 100644
index 0000000..e167f3e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalUiEvent.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.shared.log
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger.UiEventEnum
+
+/** UI events for the Communal Hub. */
+enum class CommunalUiEvent(private val id: Int) : UiEventEnum {
+    @UiEvent(doc = "Communal Hub is fully shown") COMMUNAL_HUB_SHOWN(1566),
+    @UiEvent(doc = "Communal Hub starts entering") COMMUNAL_HUB_ENTERING(1575),
+    @UiEvent(doc = "Communal Hub starts exiting") COMMUNAL_HUB_EXITING(1576),
+    @UiEvent(doc = "Communal Hub is fully gone") COMMUNAL_HUB_GONE(1577),
+    @UiEvent(doc = "Communal Hub times out") COMMUNAL_HUB_TIMEOUT(1578),
+    @UiEvent(doc = "The visible content in the Communal Hub is fully loaded and rendered")
+    COMMUNAL_HUB_LOADED(1579),
+    @UiEvent(doc = "User starts the swipe gesture to enter the Communal Hub")
+    COMMUNAL_HUB_SWIPE_TO_ENTER_START(1580),
+    @UiEvent(doc = "User finishes the swipe gesture to enter the Communal Hub")
+    COMMUNAL_HUB_SWIPE_TO_ENTER_FINISH(1581),
+    @UiEvent(doc = "User cancels the swipe gesture to enter the Communal Hub")
+    COMMUNAL_HUB_SWIPE_TO_ENTER_CANCEL(1582),
+    @UiEvent(doc = "User starts the swipe gesture to exit the Communal Hub")
+    COMMUNAL_HUB_SWIPE_TO_EXIT_START(1583),
+    @UiEvent(doc = "User finishes the swipe gesture to exit the Communal Hub")
+    COMMUNAL_HUB_SWIPE_TO_EXIT_FINISH(1584),
+    @UiEvent(doc = "User cancels the swipe gesture to exit the Communal Hub")
+    COMMUNAL_HUB_SWIPE_TO_EXIT_CANCEL(1585),
+    @UiEvent(doc = "User starts the drag gesture to reorder a widget")
+    COMMUNAL_HUB_REORDER_WIDGET_START(1586),
+    @UiEvent(doc = "User finishes the drag gesture to reorder a widget")
+    COMMUNAL_HUB_REORDER_WIDGET_FINISH(1587),
+    @UiEvent(doc = "User cancels the drag gesture to reorder a widget")
+    COMMUNAL_HUB_REORDER_WIDGET_CANCEL(1588),
+    @UiEvent(doc = "Edit mode for the Communal Hub is shown") COMMUNAL_HUB_EDIT_MODE_SHOWN(1569),
+    @UiEvent(doc = "Edit mode for the Communal Hub is gone") COMMUNAL_HUB_EDIT_MODE_GONE(1589),
+    @UiEvent(doc = "Widget picker for the Communal Hub is shown")
+    COMMUNAL_HUB_WIDGET_PICKER_SHOWN(1590),
+    @UiEvent(doc = "Widget picker for the Communal Hub is gone")
+    COMMUNAL_HUB_WIDGET_PICKER_GONE(1591),
+    @UiEvent(doc = "User performs a swipe up gesture from bottom to enter bouncer")
+    COMMUNAL_HUB_SWIPE_UP_TO_BOUNCER(1573),
+    @UiEvent(doc = "User performs a swipe down gesture from top to enter shade")
+    COMMUNAL_HUB_SWIPE_DOWN_TO_SHADE(1574);
+
+    override fun getId(): Int {
+        return id
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index c34a8df..4fabd97 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -103,6 +103,9 @@
     /** Called as the UI requests opening the widget editor. */
     open fun onOpenWidgetEditor() {}
 
+    /** Called as the UI requests to dismiss the CTA tile. */
+    open fun onDismissCtaTile() {}
+
     /** Gets the interaction handler used to handle taps on a remote view */
     abstract fun getInteractionHandler(): RemoteViews.InteractionHandler
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index da7bd34..d8e831c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -28,6 +28,7 @@
 import javax.inject.Named
 import javax.inject.Provider
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
 
 /** The view model for communal hub in edit mode. */
 @SysUISingleton
@@ -42,9 +43,11 @@
 
     override val isEditMode = true
 
-    // Only widgets are editable.
+    // Only widgets are editable. The CTA tile comes last in the list and remains visible.
     override val communalContent: Flow<List<CommunalContentModel>> =
-        communalInteractor.widgetContent
+        communalInteractor.widgetContent.map { widgets ->
+            widgets + listOf(CommunalContentModel.CtaTileInEditMode())
+        }
 
     override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 2fae8b5..066e897 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -54,15 +54,18 @@
                 return@flatMapLatest flowOf(communalInteractor.tutorialContent)
             }
             combine(
-                communalInteractor.smartspaceContent,
-                communalInteractor.umoContent,
+                communalInteractor.ongoingContent,
                 communalInteractor.widgetContent,
-            ) { smartspace, umo, widgets ->
-                smartspace + umo + widgets
+                communalInteractor.ctaTileContent,
+            ) { ongoing, widgets, ctaTile,
+                ->
+                ongoing + widgets + ctaTile
             }
         }
 
     override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor()
 
+    override fun onDismissCtaTile() = communalInteractor.dismissCtaTile()
+
     override fun getInteractionHandler(): RemoteViews.InteractionHandler = interactionHandler
 }
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 615b503..3bc4f34 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -32,6 +32,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.FaceScanningOverlay
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.log.ScreenDecorationsLogger
@@ -41,19 +42,20 @@
 
 @SysUISingleton
 class FaceScanningProviderFactory @Inject constructor(
-    private val authController: AuthController,
-    private val context: Context,
-    private val statusBarStateController: StatusBarStateController,
-    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    @Main private val mainExecutor: Executor,
-    private val logger: ScreenDecorationsLogger,
+        private val authController: AuthController,
+        private val context: Context,
+        private val statusBarStateController: StatusBarStateController,
+        private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+        @Main private val mainExecutor: Executor,
+        private val logger: ScreenDecorationsLogger,
+        private val facePropertyRepository: FacePropertyRepository,
 ) : DecorProviderFactory() {
     private val display = context.display
     private val displayInfo = DisplayInfo()
 
     override val hasProviders: Boolean
         get() {
-            if (authController.faceSensorLocation == null) {
+            if (facePropertyRepository.sensorLocation.value == null) {
                 return false
             }
 
@@ -86,6 +88,7 @@
                                         keyguardUpdateMonitor,
                                         mainExecutor,
                                         logger,
+                                        facePropertyRepository,
                                 )
                         )
                     }
@@ -104,12 +107,13 @@
 }
 
 class FaceScanningOverlayProviderImpl(
-    override val alignedBound: Int,
-    private val authController: AuthController,
-    private val statusBarStateController: StatusBarStateController,
-    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val mainExecutor: Executor,
-    private val logger: ScreenDecorationsLogger,
+        override val alignedBound: Int,
+        private val authController: AuthController,
+        private val statusBarStateController: StatusBarStateController,
+        private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+        private val mainExecutor: Executor,
+        private val logger: ScreenDecorationsLogger,
+        private val facePropertyRepository: FacePropertyRepository,
 ) : BoundDecorProvider() {
     override val viewId: Int = com.android.systemui.res.R.id.face_scanning_anim
 
@@ -162,8 +166,9 @@
         layoutParams.let { lp ->
             lp.width = ViewGroup.LayoutParams.MATCH_PARENT
             lp.height = ViewGroup.LayoutParams.MATCH_PARENT
-            logger.faceSensorLocation(authController.faceSensorLocation)
-            authController.faceSensorLocation?.y?.let { faceAuthSensorHeight ->
+            logger.faceSensorLocation(facePropertyRepository.sensorLocation.value)
+            facePropertyRepository.sensorLocation.value?.y?.let {
+                faceAuthSensorHeight ->
                 val faceScanningHeight = (faceAuthSensorHeight * 2)
                 when (rotation) {
                     Surface.ROTATION_0, Surface.ROTATION_180 ->
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 38c7c6a..699532c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -448,11 +448,6 @@
     // TODO(b/270987164): Tracking Bug
     @JvmField val TRACKPAD_GESTURE_FEATURES = releasedFlag("trackpad_gesture_features")
 
-    // TODO(b/265639042): Tracking Bug
-    @JvmField
-    val WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM =
-        unreleasedFlag("persist.wm.debug.predictive_back_qs_dialog_anim", teamfood = true)
-
     // TODO(b/273800936): Tracking Bug
     @JvmField val TRACKPAD_GESTURE_COMMON = releasedFlag("trackpad_gesture_common")
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 2f937bc..704ebdd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -21,6 +21,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.repository.FacePropertyRepository
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.common.shared.model.Position
@@ -277,6 +278,7 @@
     @Main private val mainDispatcher: CoroutineDispatcher,
     @Application private val scope: CoroutineScope,
     private val systemClock: SystemClock,
+    facePropertyRepository: FacePropertyRepository,
 ) : KeyguardRepository {
     private val _dismissAction: MutableStateFlow<DismissAction> =
         MutableStateFlow(DismissAction.None)
@@ -599,27 +601,7 @@
         awaitClose { authController.removeCallback(callback) }
     }
 
-    override val faceSensorLocation: Flow<Point?> = conflatedCallbackFlow {
-        fun sendSensorLocation() {
-            trySendWithFailureLogging(
-                authController.faceSensorLocation,
-                TAG,
-                "AuthController.Callback#onFingerprintLocationChanged"
-            )
-        }
-
-        val callback =
-            object : AuthController.Callback {
-                override fun onFaceSensorLocationChanged() {
-                    sendSensorLocation()
-                }
-            }
-
-        authController.addCallback(callback)
-        sendSensorLocation()
-
-        awaitClose { authController.removeCallback(callback) }
-    }
+    override val faceSensorLocation: Flow<Point?> = facePropertyRepository.sensorLocation
 
     override val biometricUnlockSource: Flow<BiometricUnlockSource?> = conflatedCallbackFlow {
         val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
index b98e9c2..5caa27f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
@@ -81,9 +81,12 @@
     /** Set from the notification and used as fallback when PlaybackState cannot be determined */
     val isClearable: Boolean = true,
 
-    /** Timestamp when this player was last active. */
+    /** Milliseconds since boot when this player was last active. */
     var lastActive: Long = 0L,
 
+    /** Timestamp in milliseconds when this player was created. */
+    var createdTimestampMillis: Long = 0L,
+
     /** Instance ID for logging purposes */
     val instanceId: InstanceId,
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 3e8b49d..47df3b7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -400,7 +400,12 @@
             val oldKey = findExistingEntry(key, sbn.packageName)
             if (oldKey == null) {
                 val instanceId = logger.getNewInstanceId()
-                val temp = LOADING.copy(packageName = sbn.packageName, instanceId = instanceId)
+                val temp =
+                    LOADING.copy(
+                        packageName = sbn.packageName,
+                        instanceId = instanceId,
+                        createdTimestampMillis = systemClock.currentTimeMillis(),
+                    )
                 mediaEntries.put(key, temp)
                 isNewlyActiveEntry = true
             } else if (oldKey != key) {
@@ -454,7 +459,8 @@
                     resumeAction = action,
                     hasCheckedForResume = true,
                     instanceId = instanceId,
-                    appUid = appUid
+                    appUid = appUid,
+                    createdTimestampMillis = systemClock.currentTimeMillis(),
                 )
             mediaEntries.put(packageName, resumeData)
             logSingleVsMultipleMediaAdded(appUid, packageName, instanceId)
@@ -732,6 +738,7 @@
 
         val mediaAction = getResumeMediaAction(resumeAction)
         val lastActive = systemClock.elapsedRealtime()
+        val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
         foregroundExecutor.execute {
             onMediaDataLoaded(
                 packageName,
@@ -757,6 +764,7 @@
                     notificationKey = packageName,
                     hasCheckedForResume = true,
                     lastActive = lastActive,
+                    createdTimestampMillis = createdTimestampMillis,
                     instanceId = instanceId,
                     appUid = appUid,
                     isExplicit = isExplicit,
@@ -907,6 +915,7 @@
         }
 
         val lastActive = systemClock.elapsedRealtime()
+        val createdTimestampMillis = currentEntry?.createdTimestampMillis ?: 0L
         foregroundExecutor.execute {
             val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
             val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
@@ -937,6 +946,7 @@
                     isPlaying = isPlaying,
                     isClearable = !sbn.isOngoing,
                     lastActive = lastActive,
+                    createdTimestampMillis = createdTimestampMillis,
                     instanceId = instanceId,
                     appUid = appUid,
                     isExplicit = isExplicit,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index 6ec46f6..df6843d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -61,6 +61,7 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.res.R;
+import com.android.systemui.shared.navigationbar.KeyButtonRipple;
 import com.android.systemui.shared.system.QuickStepContract;
 
 public class KeyButtonView extends ImageView implements ButtonInterface {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
index 36dc743..a01d658 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsController.java
@@ -67,9 +67,7 @@
                 synchronized (mListeners) {
                     if (setting != null && mListeners.size() != 0) {
                         if (setting.equals(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED)) {
-                            for (Listener listener : mListeners) {
-                                listener.onActivated(mManager.isReduceBrightColorsActivated());
-                            }
+                            dispatchOnActivated(mManager.isReduceBrightColorsActivated());
                         }
                     }
                 }
@@ -125,6 +123,13 @@
         mManager.setReduceBrightColorsActivated(activated);
     }
 
+    private void dispatchOnActivated(boolean activated) {
+        ArrayList<Listener> copy = new ArrayList<>(mListeners);
+        for (Listener l : copy) {
+            l.onActivated(activated);
+        }
+    }
+
     /**
      * Listener invoked whenever the Reduce Bright Colors settings are changed.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
index e075e76..2b8c335 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.res.R
+import com.android.systemui.util.time.SystemClock
 import java.time.Instant
 import java.time.LocalDateTime
 import java.time.format.DateTimeFormatter
@@ -36,10 +37,12 @@
 constructor(
     @Main private val resources: Resources,
     private val theme: Theme,
+    private val clock: SystemClock,
 ) : QSTileDataToStateMapper<AlarmTileModel> {
     companion object {
         val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E hh:mm a")
         val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E HH:mm")
+        val formatterDateOnly: DateTimeFormatter = DateTimeFormatter.ofPattern("E MMM d")
     }
     override fun map(config: QSTileConfig, data: AlarmTileModel): QSTileState =
         QSTileState.build(resources, theme, config.uiConfig) {
@@ -47,14 +50,32 @@
                 is AlarmTileModel.NextAlarmSet -> {
                     activationState = QSTileState.ActivationState.ACTIVE
 
-                    val localDateTime =
+                    val alarmDateTime =
                         LocalDateTime.ofInstant(
                             Instant.ofEpochMilli(data.alarmClockInfo.triggerTime),
                             TimeZone.getDefault().toZoneId()
                         )
-                    secondaryLabel =
-                        if (data.is24HourFormat) formatter24Hour.format(localDateTime)
-                        else formatter12Hour.format(localDateTime)
+
+                    val nowDateTime =
+                        LocalDateTime.ofInstant(
+                            Instant.ofEpochMilli(clock.currentTimeMillis()),
+                            TimeZone.getDefault().toZoneId()
+                        )
+
+                    // Edge case: If it's 8:00:30 right now and alarm is requested for next week at
+                    // 8:00:29, we still want to show the date. Same at nanosecond level.
+                    val nextWeekThisTime = nowDateTime.plusWeeks(1).withSecond(0).withNano(0)
+
+                    // is the alarm over a week away?
+                    val shouldShowDateAndHideTime = alarmDateTime >= nextWeekThisTime
+
+                    if (shouldShowDateAndHideTime) {
+                        secondaryLabel = formatterDateOnly.format(alarmDateTime)
+                    } else {
+                        secondaryLabel =
+                            if (data.is24HourFormat) formatter24Hour.format(alarmDateTime)
+                            else formatter12Hour.format(alarmDateTime)
+                    }
                 }
                 is AlarmTileModel.NoAlarmSet -> {
                     activationState = QSTileState.ActivationState.INACTIVE
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index a957095..32cd56c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.dagger;
 
+import static com.android.systemui.Flags.predictiveBackAnimateDialogs;
+
 import android.content.Context;
 import android.os.RemoteException;
 import android.service.dreams.IDreamManager;
@@ -31,8 +33,6 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpHandler;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.settings.DisplayTracker;
@@ -230,11 +230,11 @@
     /** */
     @Provides
     @SysUISingleton
-    static AnimationFeatureFlags provideAnimationFeatureFlags(FeatureFlags featureFlags) {
+    static AnimationFeatureFlags provideAnimationFeatureFlags() {
         return new AnimationFeatureFlags() {
             @Override
             public boolean isPredictiveBackQsDialogAnim() {
-                return featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM);
+                return predictiveBackAnimateDialogs();
             }
         };
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
index 38a6d39..13d7924 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ComponentSystemUIDialog.kt
@@ -34,7 +34,6 @@
 import androidx.savedstate.setViewTreeSavedStateRegistryOwner
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.model.SysUiState
 
 /**
@@ -53,7 +52,6 @@
     context: Context,
     theme: Int,
     dismissOnDeviceLock: Boolean,
-    featureFlags: FeatureFlags,
     dialogManager: SystemUIDialogManager,
     sysUiState: SysUiState,
     broadcastDispatcher: BroadcastDispatcher,
@@ -63,7 +61,6 @@
         context,
         theme,
         dismissOnDeviceLock,
-        featureFlags,
         dialogManager,
         sysUiState,
         broadcastDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index abdf827..56ea00c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -104,7 +104,8 @@
     }
 
     private void notifyManagedProfileRemoved() {
-        for (Callback callback : mCallbacks) {
+        ArrayList<Callback> copy = new ArrayList<>(mCallbacks);
+        for (Callback callback : copy) {
             callback.onManagedProfileRemoved();
         }
     }
@@ -148,7 +149,8 @@
         @Override
         public void onUserChanged(int newUser, @NonNull Context userContext) {
             reloadManagedProfiles();
-            for (Callback callback : mCallbacks) {
+            ArrayList<Callback> copy = new ArrayList<>(mCallbacks);
+            for (Callback callback : copy) {
                 callback.onManagedProfileChanged();
             }
         }
@@ -156,7 +158,8 @@
         @Override
         public void onProfilesChanged(@NonNull List<UserInfo> profiles) {
             reloadManagedProfiles();
-            for (Callback callback : mCallbacks) {
+            ArrayList<Callback> copy = new ArrayList<>(mCallbacks);
+            for (Callback callback : copy) {
                 callback.onManagedProfileChanged();
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 3394eac..390d2c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.Flags.predictiveBackAnimateDialogs;
+
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.BroadcastReceiver;
@@ -45,8 +47,6 @@
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.Application;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.res.R;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -78,7 +78,6 @@
     public static final boolean DEFAULT_DISMISS_ON_DEVICE_LOCK = true;
 
     private final Context mContext;
-    private final FeatureFlags mFeatureFlags;
     private final DialogDelegate<SystemUIDialog> mDelegate;
     @Nullable private final DismissReceiver mDismissReceiver;
     private final Handler mHandler = new Handler();
@@ -110,7 +109,6 @@
         // SystemUIDialogFactory and make all other dialogs create a SystemUIDialog to which we set
         // the content and attach listeners.
         this(context, theme, dismissOnDeviceLock,
-                Dependency.get(FeatureFlags.class),
                 Dependency.get(SystemUIDialogManager.class),
                 Dependency.get(SysUiState.class),
                 Dependency.get(BroadcastDispatcher.class),
@@ -119,7 +117,6 @@
 
     public static class Factory {
         private final Context mContext;
-        private final FeatureFlags mFeatureFlags;
         private final SystemUIDialogManager mSystemUIDialogManager;
         private final SysUiState mSysUiState;
         private final BroadcastDispatcher mBroadcastDispatcher;
@@ -128,13 +125,11 @@
         @Inject
         public Factory(
                 @Application Context context,
-                FeatureFlags featureFlags,
                 SystemUIDialogManager systemUIDialogManager,
                 SysUiState sysUiState,
                 BroadcastDispatcher broadcastDispatcher,
                 DialogLaunchAnimator dialogLaunchAnimator) {
             mContext = context;
-            mFeatureFlags = featureFlags;
             mSystemUIDialogManager = systemUIDialogManager;
             mSysUiState = sysUiState;
             mBroadcastDispatcher = broadcastDispatcher;
@@ -177,7 +172,6 @@
                     context,
                     DEFAULT_THEME,
                     DEFAULT_DISMISS_ON_DEVICE_LOCK,
-                    mFeatureFlags,
                     mSystemUIDialogManager,
                     mSysUiState,
                     mBroadcastDispatcher,
@@ -190,7 +184,6 @@
             Context context,
             int theme,
             boolean dismissOnDeviceLock,
-            FeatureFlags featureFlags,
             SystemUIDialogManager dialogManager,
             SysUiState sysUiState,
             BroadcastDispatcher broadcastDispatcher,
@@ -199,7 +192,6 @@
                 context,
                 theme,
                 dismissOnDeviceLock,
-                featureFlags,
                 dialogManager,
                 sysUiState,
                 broadcastDispatcher,
@@ -211,7 +203,6 @@
             Context context,
             int theme,
             boolean dismissOnDeviceLock,
-            FeatureFlags featureFlags,
             SystemUIDialogManager dialogManager,
             SysUiState sysUiState,
             BroadcastDispatcher broadcastDispatcher,
@@ -221,7 +212,6 @@
                 context,
                 theme,
                 dismissOnDeviceLock,
-                featureFlags,
                 dialogManager,
                 sysUiState,
                 broadcastDispatcher,
@@ -233,7 +223,6 @@
             Context context,
             int theme,
             boolean dismissOnDeviceLock,
-            FeatureFlags featureFlags,
             SystemUIDialogManager dialogManager,
             SysUiState sysUiState,
             BroadcastDispatcher broadcastDispatcher,
@@ -241,7 +230,6 @@
             DialogDelegate<SystemUIDialog> delegate) {
         super(context, theme);
         mContext = context;
-        mFeatureFlags = featureFlags;
         mDelegate = delegate;
 
         applyFlags(this);
@@ -269,7 +257,7 @@
         for (int i = 0; i < mOnCreateRunnables.size(); i++) {
             mOnCreateRunnables.get(i).run();
         }
-        if (mFeatureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM)) {
+        if (predictiveBackAnimateDialogs()) {
             DialogKt.registerAnimationOnBackInvoked(
                     /* dialog = */ this,
                     /* targetView = */ getWindow().getDecorView()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
index d91ca92..f3e8f62d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialogFactory.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.model.SysUiState
 import com.android.systemui.util.Assert
 import javax.inject.Inject
@@ -30,7 +29,6 @@
 @Inject
 constructor(
     @Application val applicationContext: Context,
-    private val featureFlags: FeatureFlagsClassic,
     private val dialogManager: SystemUIDialogManager,
     private val sysUiState: SysUiState,
     private val broadcastDispatcher: BroadcastDispatcher,
@@ -57,7 +55,6 @@
             context,
             theme,
             dismissOnDeviceLock,
-            featureFlags,
             dialogManager,
             sysUiState,
             broadcastDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 41ed76d..45078e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -20,6 +20,7 @@
 import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT;
 import static android.os.BatteryManager.EXTRA_CHARGING_STATUS;
 import static android.os.BatteryManager.EXTRA_PRESENT;
+
 import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
 import static com.android.systemui.util.DumpUtilsKt.asIndenting;
 
@@ -61,6 +62,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
 
 import javax.annotation.concurrent.GuardedBy;
 
@@ -448,50 +450,38 @@
         firePowerSaveChanged();
     }
 
+    protected final void dispatchSafeChange(Consumer<BatteryStateChangeCallback> action) {
+        ArrayList<BatteryStateChangeCallback> copy;
+        synchronized (mChangeCallbacks) {
+            copy = new ArrayList<>(mChangeCallbacks);
+        }
+        final int n = copy.size();
+        for (int i = 0; i < n; i++) {
+            action.accept(copy.get(i));
+        }
+    }
+
     protected void fireBatteryLevelChanged() {
         mLogger.logBatteryLevelChangedCallback(mLevel, mPluggedIn, mCharging);
-        synchronized (mChangeCallbacks) {
-            final int N = mChangeCallbacks.size();
-            for (int i = 0; i < N; i++) {
-                mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
-            }
-        }
+        dispatchSafeChange(
+                (callback) -> callback.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging));
     }
 
     private void fireBatteryUnknownStateChanged() {
-        synchronized (mChangeCallbacks) {
-            final int n = mChangeCallbacks.size();
-            for (int i = 0; i < n; i++) {
-                mChangeCallbacks.get(i).onBatteryUnknownStateChanged(mStateUnknown);
-            }
-        }
+        dispatchSafeChange((callback) -> callback.onBatteryUnknownStateChanged(mStateUnknown));
     }
 
     private void firePowerSaveChanged() {
-        synchronized (mChangeCallbacks) {
-            final int N = mChangeCallbacks.size();
-            for (int i = 0; i < N; i++) {
-                mChangeCallbacks.get(i).onPowerSaveChanged(mPowerSave);
-            }
-        }
+        dispatchSafeChange((callback) -> callback.onPowerSaveChanged(mPowerSave));
     }
 
     private void fireIsBatteryDefenderChanged() {
-        synchronized (mChangeCallbacks) {
-            final int n = mChangeCallbacks.size();
-            for (int i = 0; i < n; i++) {
-                mChangeCallbacks.get(i).onIsBatteryDefenderChanged(mIsBatteryDefender);
-            }
-        }
+        dispatchSafeChange((callback) -> callback.onIsBatteryDefenderChanged(mIsBatteryDefender));
     }
 
     private void fireIsIncompatibleChargingChanged() {
-        synchronized (mChangeCallbacks) {
-            final int n = mChangeCallbacks.size();
-            for (int i = 0; i < n; i++) {
-                mChangeCallbacks.get(i).onIsIncompatibleChargingChanged(mIsIncompatibleCharging);
-            }
-        }
+        dispatchSafeChange(
+                (callback) -> callback.onIsIncompatibleChargingChanged(mIsIncompatibleCharging));
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 53b343c..fc2f6e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -436,6 +436,8 @@
     @Override
     public void onServiceDisconnected() {}
 
+    // IMPORTANT: This handler guarantees that any operations on the list of callbacks is
+    // sequential, so no concurrent exceptions
     private final class H extends Handler {
         private final ArrayList<BluetoothController.Callback> mCallbacks = new ArrayList<>();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index b06ebe9..149c8fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -35,9 +35,9 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.res.R;
 import com.android.systemui.util.Utils;
 
 import java.io.PrintWriter;
@@ -291,11 +291,12 @@
 
     @VisibleForTesting
     void fireOnCastDevicesChanged() {
+        final ArrayList<Callback> callbacks;
         synchronized (mCallbacks) {
-            for (Callback callback : mCallbacks) {
-                fireOnCastDevicesChanged(callback);
-            }
-
+            callbacks = new ArrayList<>(mCallbacks);
+        }
+        for (Callback callback : callbacks) {
+            fireOnCastDevicesChanged(callback);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
index 8207012..6319781 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
@@ -36,10 +36,12 @@
     }
 
     private void handleRestrictBackgroundChanged(boolean isDataSaving) {
+        ArrayList<DataSaverController.Listener> copy;
         synchronized (mListeners) {
-            for (int i = 0; i < mListeners.size(); i++) {
-                mListeners.get(i).onDataSaverChanged(isDataSaving);
-            }
+            copy = new ArrayList<>(mListeners);
+        }
+        for (int i = 0; i < copy.size(); i++) {
+            copy.get(i).onDataSaverChanged(isDataSaving);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
index 5dcafb3..b98eff8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -202,10 +202,12 @@
 
     private void dispatchListeners(int message, boolean argument) {
         synchronized (mListeners) {
-            final int N = mListeners.size();
+            final ArrayList<WeakReference<FlashlightController.FlashlightListener>> copy =
+                    new ArrayList<>(mListeners);
+            final int n = copy.size();
             boolean cleanup = false;
-            for (int i = 0; i < N; i++) {
-                FlashlightListener l = mListeners.get(i).get();
+            for (int i = 0; i < n; i++) {
+                FlashlightListener l = copy.get(i).get();
                 if (l != null) {
                     if (message == DISPATCH_ERROR) {
                         l.onFlashlightError();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
index fffd839..87dfc99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java
@@ -119,7 +119,8 @@
             mHardwareToggleState.put(sensor, enabled);
         }
 
-        for (Callback callback : mCallbacks) {
+        Set<Callback> copy = new ArraySet<>(mCallbacks);
+        for (Callback callback : copy) {
             callback.onSensorBlockedChanged(sensor, isSensorBlocked(sensor));
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index e5f72eb..9eee5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -356,6 +356,8 @@
         updateActiveLocationRequests();
     }
 
+    // IMPORTANT: This handler guarantees that any operations on the list of callbacks is
+    // sequential, so no concurrent exceptions
     private final class H extends Handler {
         private static final int MSG_LOCATION_SETTINGS_CHANGED = 1;
         private static final int MSG_LOCATION_ACTIVE_CHANGED = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index 63b9ff9..b7d8ee3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -124,9 +124,10 @@
     }
 
     private void fireNextAlarmChanged() {
-        int n = mChangeCallbacks.size();
+        ArrayList<NextAlarmChangeCallback> copy = new ArrayList<>(mChangeCallbacks);
+        int n = copy.size();
         for (int i = 0; i < n; i++) {
-            mChangeCallbacks.get(i).onNextAlarmChanged(mNextAlarm);
+            copy.get(i).onNextAlarmChanged(mNextAlarm);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java
index f3d183c..0176abd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java
@@ -100,10 +100,13 @@
     }
 
     private void handleSafetyCenterEnableChange() {
+        final ArrayList<SafetyController.Listener> copy;
         synchronized (mListeners) {
-            for (int i = 0; i < mListeners.size(); i++) {
-                mListeners.get(i).onSafetyCenterEnableChanged(mSafetyCenterEnabled);
-            }
+            copy = new ArrayList<>(mListeners);
+        }
+        final int n = copy.size();
+        for (int i = 0; i < n; i++) {
+            copy.get(i).onSafetyCenterEnableChanged(mSafetyCenterEnabled);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 4a4d4e1..5d69f36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -50,12 +50,12 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.net.LegacyVpnInfo;
 import com.android.internal.net.VpnConfig;
-import com.android.systemui.res.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 
 import org.xmlpull.v1.XmlPullParserException;
@@ -429,10 +429,12 @@
     }
 
     private void fireCallbacks() {
+        final ArrayList<SecurityControllerCallback> copy;
         synchronized (mCallbacks) {
-            for (SecurityControllerCallback callback : mCallbacks) {
-                callback.onStateChanged();
-            }
+            copy = new ArrayList<>(mCallbacks);
+        }
+        for (SecurityControllerCallback callback : copy) {
+            callback.onStateChanged();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index 66bf527..df210b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -48,12 +48,12 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.util.Utils;
 import com.android.systemui.util.settings.GlobalSettings;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Objects;
+import java.util.function.Consumer;
 
 import javax.inject.Inject;
 
@@ -243,46 +243,43 @@
     }
 
     private void fireNextAlarmChanged() {
-        synchronized (mCallbacksLock) {
-            Utils.safeForeach(mCallbacks, c -> c.onNextAlarmChanged());
-        }
+        fireSafeChange(Callback::onNextAlarmChanged);
     }
 
     private void fireEffectsSuppressorChanged() {
-        synchronized (mCallbacksLock) {
-            Utils.safeForeach(mCallbacks, c -> c.onEffectsSupressorChanged());
-        }
+        fireSafeChange(Callback::onEffectsSupressorChanged);
     }
 
     private void fireZenChanged(int zen) {
-        synchronized (mCallbacksLock) {
-            Utils.safeForeach(mCallbacks, c -> c.onZenChanged(zen));
-        }
+        fireSafeChange(c -> c.onZenChanged(zen));
     }
 
     private void fireZenAvailableChanged(boolean available) {
-        synchronized (mCallbacksLock) {
-            Utils.safeForeach(mCallbacks, c -> c.onZenAvailableChanged(available));
-        }
+        fireSafeChange(c -> c.onZenAvailableChanged(available));
     }
 
     private void fireManualRuleChanged(ZenRule rule) {
-        synchronized (mCallbacksLock) {
-            Utils.safeForeach(mCallbacks, c -> c.onManualRuleChanged(rule));
-        }
+        fireSafeChange(c -> c.onManualRuleChanged(rule));
     }
 
     private void fireConsolidatedPolicyChanged(NotificationManager.Policy policy) {
+        fireSafeChange(c -> c.onConsolidatedPolicyChanged(policy));
+    }
+
+    private void fireSafeChange(Consumer<Callback> action) {
+        final ArrayList<Callback> copy;
         synchronized (mCallbacksLock) {
-            Utils.safeForeach(mCallbacks, c -> c.onConsolidatedPolicyChanged(policy));
+            copy = new ArrayList<>(mCallbacks);
+        }
+        final int n = copy.size();
+        for (int i = 0; i < n; i++) {
+            action.accept(copy.get(i));
         }
     }
 
     @VisibleForTesting
     protected void fireConfigChanged(ZenModeConfig config) {
-        synchronized (mCallbacksLock) {
-            Utils.safeForeach(mCallbacks, c -> c.onConfigChanged(config));
-        }
+        fireSafeChange(c -> c.onConfigChanged(config));
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
index 342494d..46936d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
@@ -25,6 +25,7 @@
 import com.android.internal.R
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
 import com.android.systemui.decor.FaceScanningProviderFactory
 import com.android.systemui.log.ScreenDecorationsLogger
 import com.android.systemui.log.logcatLogBuffer
@@ -53,6 +54,8 @@
 
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
+    private val facePropertyRepository = FakeFacePropertyRepository()
+
     private val displayId = 2
 
     @Before
@@ -86,9 +89,10 @@
                 keyguardUpdateMonitor,
                 mock(Executor::class.java),
                 ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest")),
+                facePropertyRepository,
             )
 
-        whenever(authController.faceSensorLocation).thenReturn(Point(10, 10))
+        facePropertyRepository.setSensorLocation(Point(10, 10))
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index c094df5..c07148b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -54,6 +54,7 @@
 import android.content.res.TypedArray;
 import android.graphics.Path;
 import android.graphics.PixelFormat;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.hardware.display.DisplayManager;
@@ -80,6 +81,7 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository;
 import com.android.systemui.decor.CornerDecorProvider;
 import com.android.systemui.decor.CutoutDecorProviderFactory;
 import com.android.systemui.decor.CutoutDecorProviderImpl;
@@ -101,6 +103,7 @@
 import com.android.systemui.statusbar.events.PrivacyDotViewController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.concurrency.FakeThreadFactory;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.FakeSettings;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -108,8 +111,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
@@ -169,8 +170,11 @@
     private PrivacyDotViewController.ShowingListener mPrivacyDotShowingListener;
     @Mock
     private CutoutDecorProviderFactory mCutoutFactory;
-    @Captor
-    private ArgumentCaptor<AuthController.Callback> mAuthControllerCallback;
+    @Mock
+    private JavaAdapter mJavaAdapter;
+
+    private FakeFacePropertyRepository mFakeFacePropertyRepository =
+            new FakeFacePropertyRepository();
     private List<DecorProvider> mMockCutoutList;
 
     @Before
@@ -227,20 +231,23 @@
         doAnswer(it -> !(mMockCutoutList.isEmpty())).when(mCutoutFactory).getHasProviders();
         doReturn(mMockCutoutList).when(mCutoutFactory).getProviders();
 
+        mFakeFacePropertyRepository.setSensorLocation(new Point(10, 10));
+
         mFaceScanningDecorProvider = spy(new FaceScanningOverlayProviderImpl(
                 BOUNDS_POSITION_TOP,
                 mAuthController,
                 mStatusBarStateController,
                 mKeyguardUpdateMonitor,
                 mExecutor,
-                new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer"))));
+                new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
+                mFakeFacePropertyRepository));
 
         mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings,
                 mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController,
                 mThreadFactory,
                 mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
                 new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
-                mAuthController) {
+                mFakeFacePropertyRepository, mJavaAdapter) {
             @Override
             public void start() {
                 super.start();
@@ -1235,9 +1242,9 @@
                 mSecureSettings, mCommandRegistry, mUserTracker, mDisplayTracker,
                 mDotViewController,
                 mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
-                new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mAuthController);
+                new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
+                mFakeFacePropertyRepository, mJavaAdapter);
         screenDecorations.start();
-        verify(mAuthController).addCallback(mAuthControllerCallback.capture());
         when(mContext.getDisplay()).thenReturn(mDisplay);
         when(mDisplay.getDisplayInfo(any())).thenAnswer(new Answer<Boolean>() {
             @Override
@@ -1252,9 +1259,9 @@
         });
         mExecutor.runAllReady();
         clearInvocations(mFaceScanningDecorProvider);
-
-        AuthController.Callback callback = mAuthControllerCallback.getValue();
-        callback.onFaceSensorLocationChanged();
+        final Point location = new Point();
+        mFakeFacePropertyRepository.setSensorLocation(location);
+        screenDecorations.onFaceSensorLocationChanged(location);
         mExecutor.runAllReady();
 
         verify(mFaceScanningDecorProvider).onReloadResAndMeasure(any(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
index c525711..9b6c8cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
@@ -25,14 +25,12 @@
 import android.widget.Button
 import android.widget.SeekBar
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.model.SysUiState
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK
@@ -78,7 +76,6 @@
     @Mock private lateinit var dialogManager: SystemUIDialogManager
     @Mock private lateinit var dialogFactory: SystemUIDialog.Factory
     @Mock private lateinit var userTracker: UserTracker
-    private val featureFlags = FakeFeatureFlags()
     @Mock private lateinit var sysuiState: SysUiState
     @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
 
@@ -88,7 +85,6 @@
         testableLooper = TestableLooper.get(this)
         val mainHandler = Handler(testableLooper.looper)
         systemSettings = FakeSettings()
-        featureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM, true)
         // Guarantee that the systemSettings always starts with the default font scale.
         systemSettings.putFloatForUser(Settings.System.FONT_SCALE, 1.0f, userTracker.userId)
         secureSettings = FakeSettings()
@@ -96,29 +92,32 @@
         backgroundDelayableExecutor = FakeExecutor(systemClock)
         whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
 
-        fontScalingDialogDelegate = spy(FontScalingDialogDelegate(
-                mContext,
-                dialogFactory,
-                LayoutInflater.from(mContext),
-                systemSettings,
-                secureSettings,
-                systemClock,
-                userTracker,
-                mainHandler,
-                backgroundDelayableExecutor
-            ))
+        fontScalingDialogDelegate =
+            spy(
+                FontScalingDialogDelegate(
+                    mContext,
+                    dialogFactory,
+                    LayoutInflater.from(mContext),
+                    systemSettings,
+                    secureSettings,
+                    systemClock,
+                    userTracker,
+                    mainHandler,
+                    backgroundDelayableExecutor
+                )
+            )
 
-        dialog = SystemUIDialog(
-            mContext,
-            0,
-            DEFAULT_DISMISS_ON_DEVICE_LOCK,
-            featureFlags,
-            dialogManager,
-            sysuiState,
-            fakeBroadcastDispatcher,
-            dialogLaunchAnimator,
-            fontScalingDialogDelegate
-        )
+        dialog =
+            SystemUIDialog(
+                mContext,
+                0,
+                DEFAULT_DISMISS_ON_DEVICE_LOCK,
+                dialogManager,
+                sysuiState,
+                fakeBroadcastDispatcher,
+                dialogLaunchAnimator,
+                fontScalingDialogDelegate
+            )
 
         whenever(dialogFactory.create(any(), any())).thenReturn(dialog)
     }
@@ -299,11 +298,7 @@
         // Default seekbar progress for font size is 1, simulate dragging to 0 without
         // releasing the finger
         changeListener.onStartTrackingTouch(seekBar)
-        changeListener.onProgressChanged(
-            seekBar,
-            /* progress= */ 0,
-            /* fromUser= */ false
-        )
+        changeListener.onProgressChanged(seekBar, /* progress= */ 0, /* fromUser= */ false)
         backgroundDelayableExecutor.advanceClockToNext()
         backgroundDelayableExecutor.runAllReady()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index c143bc0..a47e288 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -29,6 +29,7 @@
 import com.android.keyguard.logging.KeyguardLogger
 import com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -93,6 +94,7 @@
     @Mock
     private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal
 
+    private val facePropertyRepository = FakeFacePropertyRepository()
     private val displayMetrics = DisplayMetrics()
 
     @Captor
@@ -126,6 +128,7 @@
             KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)),
             biometricUnlockController,
             lightRevealScrim,
+            facePropertyRepository,
             rippleView,
         )
         controller.init()
@@ -202,7 +205,7 @@
 
     @Test
     fun testNullFaceSensorLocationDoesNothing() {
-        `when`(authController.faceSensorLocation).thenReturn(null)
+        facePropertyRepository.setSensorLocation(null)
         controller.onViewAttached()
 
         val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
@@ -270,7 +273,7 @@
     fun testAnimatorRunWhenWakeAndUnlock_faceUdfpsFingerDown() {
         mSetFlagsRule.disableFlags(FLAG_LIGHT_REVEAL_MIGRATION)
         val faceLocation = Point(5, 5)
-        `when`(authController.faceSensorLocation).thenReturn(faceLocation)
+        facePropertyRepository.setSensorLocation(faceLocation)
         controller.onViewAttached()
         `when`(keyguardStateController.isShowing).thenReturn(true)
         `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
index 2aeba9a..3dcb3f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
@@ -20,6 +20,7 @@
 
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.view.Surface;
 
 import androidx.test.filters.SmallTest;
@@ -63,28 +64,32 @@
         assertThat(
                 mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
                         0 /* touchX */, 0/* touchY */,
-                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+                                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
                 )
         ).isEqualTo(mTouchHints[0]);
         // touch at 90 degrees
         assertThat(
                 mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
                         0 /* touchX */, -1/* touchY */,
-                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+                                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
                 )
         ).isEqualTo(mTouchHints[1]);
         // touch at 180 degrees
         assertThat(
                 mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
                         -1 /* touchX */, 0/* touchY */,
-                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+                                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
                 )
         ).isEqualTo(mTouchHints[2]);
         // touch at 270 degrees
         assertThat(
                 mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
                         0 /* touchX */, 1/* touchY */,
-                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+                                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
                 )
         ).isEqualTo(mTouchHints[3]);
     }
@@ -97,28 +102,32 @@
         assertThat(
                 mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
                         0 /* touchX */, 0 /* touchY */,
-                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+                                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
                 )
         ).isEqualTo(mTouchHints[1]);
         // touch at 90 degrees -> 180 degrees
         assertThat(
                 mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
                         0 /* touchX */, -1 /* touchY */,
-                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+                                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
                 )
         ).isEqualTo(mTouchHints[2]);
         // touch at 180 degrees -> 270 degrees
         assertThat(
                 mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
                         -1 /* touchX */, 0 /* touchY */,
-                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+                                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
                 )
         ).isEqualTo(mTouchHints[3]);
         // touch at 270 degrees -> 0 degrees
         assertThat(
                 mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
                         0 /* touchX */, 1/* touchY */,
-                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+                                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
                 )
         ).isEqualTo(mTouchHints[0]);
     }
@@ -131,28 +140,32 @@
         assertThat(
                 mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
                         0 /* touchX */, 0/* touchY */,
-                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+                                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
                 )
         ).isEqualTo(mTouchHints[3]);
         // touch at 90 degrees -> 0 degrees
         assertThat(
                 mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
                         0 /* touchX */, -1/* touchY */,
-                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+                                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
                 )
         ).isEqualTo(mTouchHints[0]);
         // touch at 180 degrees -> 90 degrees
         assertThat(
                 mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
                         -1 /* touchX */, 0/* touchY */,
-                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+                                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
                 )
         ).isEqualTo(mTouchHints[1]);
         // touch at 270 degrees -> 180 degrees
         assertThat(
                 mUdfpsUtils.onTouchOutsideOfSensorArea(true, mContext,
                         0 /* touchX */, 1/* touchY */,
-                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation)
+                        new UdfpsOverlayParams(new Rect(), new Rect(), 0, 0, 1f, rotation,
+                                FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
                 )
         ).isEqualTo(mTouchHints[2]);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
index 834179bf..a84778a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/DisplayStateRepositoryTest.kt
@@ -19,6 +19,7 @@
 import android.hardware.devicestate.DeviceStateManager
 import android.hardware.display.DisplayManager
 import android.os.Handler
+import android.util.Size
 import android.view.Display
 import android.view.DisplayInfo
 import android.view.Surface
@@ -147,6 +148,40 @@
             displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_180)
             assertThat(currentRotation).isEqualTo(DisplayRotation.ROTATION_180)
         }
+
+    @Test
+    fun updatesCurrentSize_whenDisplayStateChanges() =
+        testScope.runTest {
+            val currentSize by collectLastValue(underTest.currentDisplaySize)
+            runCurrent()
+
+            verify(displayManager)
+                .registerDisplayListener(
+                    displayListenerCaptor.capture(),
+                    same(handler),
+                    eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED)
+                )
+
+            whenever(display.getDisplayInfo(any())).then {
+                val info = it.getArgument<DisplayInfo>(0)
+                info.rotation = Surface.ROTATION_0
+                info.logicalWidth = 100
+                info.logicalHeight = 200
+                return@then true
+            }
+            displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_0)
+            assertThat(currentSize).isEqualTo(Size(100, 200))
+
+            whenever(display.getDisplayInfo(any())).then {
+                val info = it.getArgument<DisplayInfo>(0)
+                info.rotation = Surface.ROTATION_90
+                info.logicalWidth = 100
+                info.logicalHeight = 200
+                return@then true
+            }
+            displayListenerCaptor.value.onDisplayChanged(Surface.ROTATION_180)
+            assertThat(currentSize).isEqualTo(Size(200, 100))
+        }
 }
 
 private fun DeviceStateManager.captureCallback() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
index c14ad6a..9f24d5d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FacePropertyRepositoryImplTest.kt
@@ -17,10 +17,12 @@
 
 package com.android.systemui.biometrics.data.repository
 
+import android.graphics.Point
 import android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE
 import android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT
 import android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED
 import android.hardware.biometrics.SensorProperties
+import android.hardware.camera2.CameraManager
 import android.hardware.face.FaceManager
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.hardware.face.IFaceAuthenticatorsRegisteredCallback
@@ -28,9 +30,12 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.shared.model.LockoutMode
 import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.res.R
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestDispatcher
@@ -45,6 +50,7 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.any
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
@@ -53,23 +59,56 @@
 @SmallTest
 @RunWith(JUnit4::class)
 class FacePropertyRepositoryImplTest : SysuiTestCase() {
+    companion object {
+        private const val LOGICAL_CAMERA_ID_OUTER_FRONT = "0"
+        private const val LOGICAL_CAMERA_ID_INNER_FRONT = "1"
+        private const val PHYSICAL_CAMERA_ID_OUTER_FRONT = "5"
+        private const val PHYSICAL_CAMERA_ID_INNER_FRONT = "6"
+        private val OUTER_FRONT_SENSOR_LOCATION = intArrayOf(100, 10, 20)
+        private val INNER_FRONT_SENSOR_LOCATION = intArrayOf(200, 20, 30)
+    }
+
     @JvmField @Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
 
     private lateinit var underTest: FacePropertyRepository
     private lateinit var dispatcher: TestDispatcher
     private lateinit var testScope: TestScope
 
+    private val displayStateRepository = FakeDisplayStateRepository()
+    private val configurationRepository = FakeConfigurationRepository()
+
     @Captor private lateinit var callback: ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback>
     @Mock private lateinit var faceManager: FaceManager
+    @Captor private lateinit var cameraCallback: ArgumentCaptor<CameraManager.AvailabilityCallback>
+    @Mock private lateinit var cameraManager: CameraManager
     @Before
     fun setup() {
+        overrideResource(R.string.config_protectedCameraId, LOGICAL_CAMERA_ID_OUTER_FRONT)
+        overrideResource(R.string.config_protectedPhysicalCameraId, PHYSICAL_CAMERA_ID_OUTER_FRONT)
+        overrideResource(R.string.config_protectedInnerCameraId, LOGICAL_CAMERA_ID_INNER_FRONT)
+        overrideResource(
+            R.string.config_protectedInnerPhysicalCameraId,
+            PHYSICAL_CAMERA_ID_INNER_FRONT
+        )
+        overrideResource(R.array.config_face_auth_props, OUTER_FRONT_SENSOR_LOCATION)
+        overrideResource(R.array.config_inner_face_auth_props, INNER_FRONT_SENSOR_LOCATION)
+
         dispatcher = StandardTestDispatcher()
         testScope = TestScope(dispatcher)
         underTest = createRepository(faceManager)
     }
 
     private fun createRepository(manager: FaceManager? = faceManager) =
-        FacePropertyRepositoryImpl(testScope.backgroundScope, dispatcher, manager)
+        FacePropertyRepositoryImpl(
+            context,
+            context.mainExecutor,
+            testScope.backgroundScope,
+            dispatcher,
+            manager,
+            cameraManager,
+            displayStateRepository,
+            configurationRepository,
+        )
 
     @Test
     fun whenFaceManagerIsNotPresentIsNull() =
@@ -129,6 +168,75 @@
             assertThat(underTest.getLockoutMode(userId)).isEqualTo(LockoutMode.NONE)
         }
 
+    @Test
+    fun providesTheSensorLocationOfOuterCameraFromOnPhysicalCameraAvailable() {
+        testScope.runTest {
+            runCurrent()
+            collectLastValue(underTest.sensorLocation)
+
+            verify(faceManager).addAuthenticatorsRegisteredCallback(callback.capture())
+            callback.value.onAllAuthenticatorsRegistered(
+                listOf(createSensorProperties(1, SensorProperties.STRENGTH_STRONG))
+            )
+            runCurrent()
+            verify(cameraManager)
+                .registerAvailabilityCallback(any(Executor::class.java), cameraCallback.capture())
+
+            cameraCallback.value.onPhysicalCameraAvailable("1", PHYSICAL_CAMERA_ID_OUTER_FRONT)
+            runCurrent()
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation)
+                .isEqualTo(Point(OUTER_FRONT_SENSOR_LOCATION[0], OUTER_FRONT_SENSOR_LOCATION[1]))
+        }
+    }
+
+    @Test
+    fun providesTheSensorLocationOfInnerCameraFromOnPhysicalCameraAvailable() {
+        testScope.runTest {
+            runCurrent()
+            collectLastValue(underTest.sensorLocation)
+
+            verify(faceManager).addAuthenticatorsRegisteredCallback(callback.capture())
+            callback.value.onAllAuthenticatorsRegistered(
+                listOf(createSensorProperties(1, SensorProperties.STRENGTH_STRONG))
+            )
+            runCurrent()
+            verify(cameraManager)
+                .registerAvailabilityCallback(any(Executor::class.java), cameraCallback.capture())
+
+            cameraCallback.value.onPhysicalCameraAvailable("1", PHYSICAL_CAMERA_ID_INNER_FRONT)
+            runCurrent()
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation)
+                .isEqualTo(Point(INNER_FRONT_SENSOR_LOCATION[0], INNER_FRONT_SENSOR_LOCATION[1]))
+        }
+    }
+
+    @Test
+    fun providesTheSensorLocationOfCameraFromOnPhysicalCameraUnavailable() {
+        testScope.runTest {
+            runCurrent()
+            collectLastValue(underTest.sensorLocation)
+
+            verify(faceManager).addAuthenticatorsRegisteredCallback(callback.capture())
+            callback.value.onAllAuthenticatorsRegistered(
+                listOf(createSensorProperties(1, SensorProperties.STRENGTH_STRONG))
+            )
+            runCurrent()
+            verify(cameraManager)
+                .registerAvailabilityCallback(any(Executor::class.java), cameraCallback.capture())
+
+            cameraCallback.value.onPhysicalCameraUnavailable("1", PHYSICAL_CAMERA_ID_INNER_FRONT)
+            runCurrent()
+
+            val sensorLocation by collectLastValue(underTest.sensorLocation)
+            assertThat(sensorLocation)
+                .isEqualTo(Point(OUTER_FRONT_SENSOR_LOCATION[0], OUTER_FRONT_SENSOR_LOCATION[1]))
+        }
+    }
+
     private fun createSensorProperties(id: Int, strength: Int) =
         FaceSensorPropertiesInternal(id, strength, 0, emptyList(), 1, false, false, false)
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
index 3ff43c6..7d5aec6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
@@ -17,7 +17,9 @@
 package com.android.systemui.bluetooth;
 
 import static com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK;
+
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.any;
@@ -40,8 +42,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.media.dialog.MediaOutputDialogFactory;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.res.R;
@@ -72,7 +72,6 @@
             LocalBluetoothLeBroadcast.class);
     private final BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
     private BroadcastDialogDelegate mBroadcastDialogDelegate;
-    private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     @Mock SystemUIDialog.Factory mSystemUIDialogFactory;
     @Mock SystemUIDialogManager mDialogManager;
     @Mock SysUiState mSysUiState;
@@ -91,7 +90,6 @@
         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
 
-        mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM, true);
         when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
         when(mSystemUIDialogFactory.create(any(), any())).thenReturn(mDialog);
 
@@ -110,7 +108,6 @@
                 mContext,
                 0,
                 DEFAULT_DISMISS_ON_DEVICE_LOCK,
-                mFeatureFlags,
                 mDialogManager,
                 mSysUiState,
                 getFakeBroadcastDispatcher(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
index 0a5b124..fb101dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataCombineLatestTest.java
@@ -78,7 +78,7 @@
         mMediaData = new MediaData(
                 USER_ID, true, APP, null, ARTIST, TITLE, null,
                 new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
-                MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L,
+                MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L, 0L,
                 InstanceId.fakeInstanceId(-1), -1, false, null);
         mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
index 35bf775..dc211303 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -83,7 +83,6 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM, true);
         when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
         when(mSystemUIDialogFactory.create()).thenReturn(mSystemUIDialog);
         when(mSystemUIDialog.getContext()).thenReturn(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 7ce51ae..86ab01c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -105,7 +105,6 @@
             spy(
                 SystemUIDialog.Factory(
                     context,
-                    flags,
                     systemUIDialogManager,
                     sysuiState,
                     broadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index cb90cc5..0ba99f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -44,7 +44,6 @@
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
 import com.android.systemui.mediaprojection.SessionCreationSource;
@@ -113,7 +112,6 @@
 
         mDialogFactory = new TestSystemUIDialogFactory(
                 mContext,
-                mFeatureFlags,
                 Dependency.get(SystemUIDialogManager.class),
                 Dependency.get(SysUiState.class),
                 Dependency.get(BroadcastDispatcher.class),
@@ -313,14 +311,12 @@
 
         TestSystemUIDialogFactory(
                 Context context,
-                FeatureFlags featureFlags,
                 SystemUIDialogManager systemUIDialogManager,
                 SysUiState sysUiState,
                 BroadcastDispatcher broadcastDispatcher,
                 DialogLaunchAnimator dialogLaunchAnimator) {
             super(
                     context,
-                    featureFlags,
                     systemUIDialogManager,
                     sysUiState,
                     broadcastDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index 8f696e7..2399536 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -74,7 +74,6 @@
         val systemUIDialogFactory =
             SystemUIDialog.Factory(
                 context,
-                Dependency.get(FeatureFlags::class.java),
                 Dependency.get(SystemUIDialogManager::class.java),
                 Dependency.get(SysUiState::class.java),
                 Dependency.get(BroadcastDispatcher::class.java),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
index 7eba3b46..c44c178 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
@@ -22,11 +22,15 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.settings.UserTracker
 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.time.FakeSystemClock
 import junit.framework.Assert
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
+import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
@@ -72,6 +76,34 @@
         Assert.assertEquals(true, controller.hasActiveProfile())
     }
 
+    @Test
+    fun callbackRemovedWhileDispatching_doesntCrash() {
+        var remove = false
+        val callback =
+            object : ManagedProfileController.Callback {
+                override fun onManagedProfileChanged() {
+                    if (remove) {
+                        controller.removeCallback(this)
+                    }
+                }
+
+                override fun onManagedProfileRemoved() {
+                    if (remove) {
+                        controller.removeCallback(this)
+                    }
+                }
+            }
+        controller.addCallback(callback)
+        controller.addCallback(TestCallback)
+
+        remove = true
+        setupWorkingProfile(1)
+
+        val captor = argumentCaptor<UserTracker.Callback>()
+        verify(userTracker).addCallback(capture(captor), any())
+        captor.value.onProfilesChanged(userManager.getEnabledProfiles(1))
+    }
+
     private fun setupWorkingProfile(userId: Int) {
         `when`(userManager.getEnabledProfiles(userId))
             .thenReturn(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 2f79955..a5c766d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -17,10 +17,12 @@
 package com.android.systemui.statusbar.policy;
 
 import static android.os.BatteryManager.EXTRA_PRESENT;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
 import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -59,6 +61,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -286,6 +289,24 @@
         Assert.assertFalse(mBatteryController.isIncompatibleCharging());
     }
 
+    @Test
+    public void callbackRemovedWhileDispatching_doesntCrash() {
+        final AtomicBoolean remove = new AtomicBoolean(false);
+        BatteryStateChangeCallback callback = new BatteryStateChangeCallback() {
+            @Override
+            public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+                if (remove.get()) {
+                    mBatteryController.removeCallback(this);
+                }
+            }
+        };
+        mBatteryController.addCallback(callback);
+        // Add another callback so the iteration continues
+        mBatteryController.addCallback(new BatteryStateChangeCallback() {});
+        remove.set(true);
+        mBatteryController.fireBatteryLevelChanged();
+    }
+
     private void setupIncompatibleCharging() {
         final List<UsbPort> usbPorts = new ArrayList<>();
         usbPorts.add(mUsbPort);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
index b8e4306..68c1b8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/CastControllerImplTest.java
@@ -127,6 +127,25 @@
         }
     }
 
+    /** Regression test for b/317700495 */
+    @Test
+    public void removeCallbackWhileIterating_doesntCrash() {
+        final AtomicBoolean remove = new AtomicBoolean(false);
+        Callback callback = new Callback() {
+            @Override
+            public void onCastDevicesChanged() {
+                if (remove.get()) {
+                    mController.removeCallback(this);
+                }
+            }
+        };
+        mController.addCallback(callback);
+        // Add another callback so the iteration continues
+        mController.addCallback(() -> {});
+        remove.set(true);
+        mController.fireOnCastDevicesChanged();
+    }
+
     @Test
     public void hasConnectedCastDevice_connected() {
         CastController.CastDevice castDevice = new CastController.CastDevice();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
index db0029a..777fa28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
@@ -26,6 +26,8 @@
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.time.FakeSystemClock
 import java.util.concurrent.Executor
@@ -128,11 +130,42 @@
         verify(cameraManager).setTorchMode(id, enable)
     }
 
+    @Test
+    fun testCallbackRemovedWhileDispatching_doesntCrash() {
+        injectCamera()
+        var remove = false
+        val callback = object : FlashlightController.FlashlightListener {
+            override fun onFlashlightChanged(enabled: Boolean) {
+                if (remove) {
+                    controller.removeCallback(this)
+                }
+            }
+
+            override fun onFlashlightError() {}
+
+            override fun onFlashlightAvailabilityChanged(available: Boolean) {}
+        }
+        controller.addCallback(callback)
+        controller.addCallback(object : FlashlightController.FlashlightListener {
+            override fun onFlashlightChanged(enabled: Boolean) {}
+
+            override fun onFlashlightError() {}
+
+            override fun onFlashlightAvailabilityChanged(available: Boolean) {}
+        })
+        backgroundExecutor.runAllReady()
+
+        val captor = argumentCaptor<CameraManager.TorchCallback>()
+        verify(cameraManager).registerTorchCallback(any(), capture(captor))
+        remove = true
+        captor.value.onTorchModeChanged(ID, true)
+    }
+
     private fun injectCamera(
         flash: Boolean = true,
         facing: Int = CameraCharacteristics.LENS_FACING_BACK
     ): String {
-        val cameraID = "ID"
+        val cameraID = ID
         val camera = CameraCharacteristics(CameraMetadataNative().apply {
             set(CameraCharacteristics.FLASH_INFO_AVAILABLE, flash)
             set(CameraCharacteristics.LENS_FACING, facing)
@@ -141,4 +174,8 @@
         `when`(cameraManager.getCameraCharacteristics(cameraID)).thenReturn(camera)
         return cameraID
     }
+
+    companion object {
+        private const val ID = "ID"
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
index 89989ce..b03edaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
@@ -27,6 +27,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertEquals
 import org.junit.Before
 import org.junit.Test
@@ -130,4 +131,61 @@
         controller.mPermControllerChangeReceiver.onReceive(context, testIntent)
         verify(listener, never()).onSafetyCenterEnableChanged(true)
     }
+
+    @Test
+    fun listenerRemovedWhileDispatching_doesNotCrash() {
+        var remove = false
+        val callback = object : SafetyController.Listener {
+            override fun onSafetyCenterEnableChanged(isSafetyCenterEnabled: Boolean) {
+                if (remove) {
+                    controller.removeCallback(this)
+                }
+            }
+        }
+
+        controller.addCallback(callback)
+        controller.addCallback {}
+
+        remove = true
+
+        `when`(scm.isSafetyCenterEnabled).thenReturn(true)
+        val testIntent = Intent(Intent.ACTION_PACKAGE_CHANGED)
+        testIntent.data = Uri.parse("package:$TEST_PC_PKG")
+        controller.mPermControllerChangeReceiver.onReceive(context, testIntent)
+    }
+
+    @Test
+    fun listenerRemovedWhileDispatching_otherCallbacksCalled() {
+        var remove = false
+        var called = false
+
+        val callback1 = object : SafetyController.Listener {
+            override fun onSafetyCenterEnableChanged(isSafetyCenterEnabled: Boolean) {
+                if (remove) {
+                    controller.removeCallback(this)
+                }
+            }
+        }
+
+        val callback2 = object : SafetyController.Listener {
+            override fun onSafetyCenterEnableChanged(isSafetyCenterEnabled: Boolean) {
+                // When the first callback is removed, we track if this is called
+                if (remove) {
+                    called = true
+                }
+            }
+        }
+
+        controller.addCallback(callback1)
+        controller.addCallback(callback2)
+
+        remove = true
+
+        `when`(scm.isSafetyCenterEnabled).thenReturn(true)
+        val testIntent = Intent(Intent.ACTION_PACKAGE_CHANGED)
+        testIntent.data = Uri.parse("package:$TEST_PC_PKG")
+        controller.mPermControllerChangeReceiver.onReceive(context, testIntent)
+
+        assertThat(called).isTrue()
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
index c35bc69..1dab84e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -63,6 +63,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -217,6 +218,28 @@
                 ), any(NetworkCallback.class));
     }
 
+    @Test
+    public void testRemoveCallbackWhileDispatch_doesntCrash() {
+        final AtomicBoolean remove = new AtomicBoolean(false);
+        SecurityController.SecurityControllerCallback callback =
+                new SecurityController.SecurityControllerCallback() {
+                    @Override
+                    public void onStateChanged() {
+                        if (remove.get()) {
+                            mSecurityController.removeCallback(this);
+                        }
+                    }
+                };
+        mSecurityController.addCallback(callback);
+        // Add another callback so the iteration continues
+        mSecurityController.addCallback(() -> {});
+        mBgExecutor.runAllReady();
+        remove.set(true);
+
+        mSecurityController.onUserSwitched(10);
+        mBgExecutor.runAllReady();
+    }
+
     /**
      * refresh CA certs by sending a user unlocked broadcast for the desired user
      */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
index 6825f65..f1a2c28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ZenModeControllerImplTest.java
@@ -47,6 +47,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
 @SmallTest
@@ -174,4 +175,26 @@
         }
 
     }
+
+    @Test
+    public void testCallbackRemovedWhileDispatching_doesntCrash() {
+        final AtomicBoolean remove = new AtomicBoolean(false);
+        mGlobalSettings.putInt(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
+        TestableLooper.get(this).processAllMessages();
+        final ZenModeController.Callback callback = new ZenModeController.Callback() {
+            @Override
+            public void onZenChanged(int zen) {
+                if (remove.get()) {
+                    mController.removeCallback(this);
+                }
+            }
+        };
+        mController.addCallback(callback);
+        mController.addCallback(new ZenModeController.Callback() {});
+
+        remove.set(true);
+
+        mGlobalSettings.putInt(Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_NO_INTERRUPTIONS);
+        TestableLooper.get(this).processAllMessages();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
index 4e61b89..2bc05fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProviderTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.unfold.progress
 
+import android.os.Handler
+import android.os.HandlerThread
 import android.os.Looper
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
@@ -24,6 +26,7 @@
 import com.android.systemui.unfold.TestUnfoldTransitionProvider
 import com.android.systemui.utils.os.FakeHandler
 import kotlin.test.Test
+import kotlinx.coroutines.test.runTest
 import org.junit.runner.RunWith
 
 @RunWith(AndroidTestingRunner::class)
@@ -93,4 +96,13 @@
 
         listener.assertNotStarted()
     }
+
+    @Test
+    fun addCallback_fromBackgroundThread_succeeds() = runTest {
+        val bgHandler = Handler(HandlerThread("TestBgThread").apply { start() }.looper)
+        bgHandler.runWithScissors({ progressProvider.addCallback(listener) }, 5000L)
+
+        wrappedProgressProvider.onTransitionStarted()
+        listener.assertStarted()
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
index 3fdeb30..3b5ff38 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.biometrics.data.repository
 
+import android.util.Size
 import com.android.systemui.biometrics.shared.model.DisplayRotation
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -29,6 +30,9 @@
     private val _currentRotation = MutableStateFlow<DisplayRotation>(DisplayRotation.ROTATION_0)
     override val currentRotation: StateFlow<DisplayRotation> = _currentRotation.asStateFlow()
 
+    private val _currentDisplaySize = MutableStateFlow<Size>(Size(0, 0))
+    override val currentDisplaySize: StateFlow<Size> = _currentDisplaySize.asStateFlow()
+
     override val isReverseDefaultRotation = false
 
     fun setIsInRearDisplayMode(isInRearDisplayMode: Boolean) {
@@ -38,4 +42,8 @@
     fun setCurrentRotation(currentRotation: DisplayRotation) {
         _currentRotation.value = currentRotation
     }
+
+    fun setCurrentDisplaySize(size: Size) {
+        _currentDisplaySize.value = size
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
index 51ce9f0..77f501f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFacePropertyRepository.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.biometrics.data.repository
 
+import android.graphics.Point
 import com.android.systemui.biometrics.shared.model.LockoutMode
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -28,6 +29,10 @@
 
     private val lockoutModesForUser = mutableMapOf<Int, LockoutMode>()
 
+    private val faceSensorLocation = MutableStateFlow<Point?>(null)
+    override val sensorLocation: StateFlow<Point?>
+        get() = faceSensorLocation
+
     fun setLockoutMode(userId: Int, mode: LockoutMode) {
         lockoutModesForUser[userId] = mode
     }
@@ -38,4 +43,8 @@
     fun setSensorInfo(value: FaceSensorInfo?) {
         faceSensorInfo.value = value
     }
+
+    fun setSensorLocation(value: Point?) {
+        faceSensorLocation.value = value
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
index 3ab1b6c..3ea3ccf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalMediaRepository.kt
@@ -16,8 +16,24 @@
 
 package com.android.systemui.communal.data.repository
 
+import com.android.systemui.communal.data.model.CommunalMediaModel
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
-class FakeCommunalMediaRepository(
-    override val mediaPlaying: MutableStateFlow<Boolean> = MutableStateFlow(false)
-) : CommunalMediaRepository
+class FakeCommunalMediaRepository : CommunalMediaRepository {
+    private val _mediaModel = MutableStateFlow(CommunalMediaModel.INACTIVE)
+
+    override val mediaModel: Flow<CommunalMediaModel> = _mediaModel
+
+    fun mediaActive(timestamp: Long = 0L) {
+        _mediaModel.value =
+            CommunalMediaModel(
+                hasAnyMediaOrRecommendation = true,
+                createdTimestampMillis = timestamp,
+            )
+    }
+
+    fun mediaInactive() {
+        _mediaModel.value = CommunalMediaModel.INACTIVE
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index c85c27e..e82cae4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -9,6 +9,7 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.stateIn
@@ -52,4 +53,12 @@
     fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) {
         _isCommunalHubShowing.value = isCommunalHubShowing
     }
+
+    private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
+    override val isCtaTileInViewModeVisible: Flow<Boolean> =
+        _isCtaTileInViewModeVisible.asStateFlow()
+
+    override fun setCtaTileInViewModeVisibility(isVisible: Boolean) {
+        _isCtaTileInViewModeVisible.value = isVisible
+    }
 }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt
index 9bdf3d5..fdce147 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/MainThreadUnfoldTransitionProgressProvider.kt
@@ -24,12 +24,16 @@
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import java.util.Collections.synchronizedMap
 
 /**
  * [UnfoldTransitionProgressProvider] that forwards all progress to the main thread handler.
  *
  * This is needed when progress are calculated in the background, but some listeners need the
  * callbacks in the main thread.
+ *
+ * Note that this class assumes that the root provider has thread safe callback registration, as
+ * they might be called from any thread.
  */
 class MainThreadUnfoldTransitionProgressProvider
 @AssistedInject
@@ -38,27 +42,20 @@
     @Assisted private val rootProvider: UnfoldTransitionProgressProvider
 ) : UnfoldTransitionProgressProvider {
 
-    private val listenerMap = mutableMapOf<TransitionProgressListener, TransitionProgressListener>()
+    private val listenerMap: MutableMap<TransitionProgressListener, TransitionProgressListener> =
+        synchronizedMap(mutableMapOf())
 
     override fun addCallback(listener: TransitionProgressListener) {
-        assertMainThread()
         val proxy = TransitionProgressListerProxy(listener)
         rootProvider.addCallback(proxy)
         listenerMap[listener] = proxy
     }
 
     override fun removeCallback(listener: TransitionProgressListener) {
-        assertMainThread()
         val proxy = listenerMap.remove(listener) ?: return
         rootProvider.removeCallback(proxy)
     }
 
-    private fun assertMainThread() {
-        check(mainHandler.looper.isCurrentThread) {
-            "Should be called from the main thread, but this is ${Thread.currentThread()}"
-        }
-    }
-
     override fun destroy() {
         rootProvider.destroy()
     }
diff --git a/packages/overlays/NoCutoutOverlay/res/values/config.xml b/packages/overlays/NoCutoutOverlay/res/values/config.xml
index ed0340b..b44a153a 100644
--- a/packages/overlays/NoCutoutOverlay/res/values/config.xml
+++ b/packages/overlays/NoCutoutOverlay/res/values/config.xml
@@ -20,10 +20,17 @@
          black in software (to avoid aliasing or emulate a cutout that is not physically existent).
      -->
     <bool name="config_fillMainBuiltInDisplayCutout">false</bool>
+    <!-- Whether the display cutout region of the secondary built-in display should be forced to
+         black in software (to avoid aliasing or emulate a cutout that is not physically existent).
+     -->
+    <bool name="config_fillSecondaryBuiltInDisplayCutout">false</bool>
 
     <!-- If true, and there is a cutout on the main built in display, the cutout will be masked
          by shrinking the display such that it does not overlap the cutout area. -->
     <bool name="config_maskMainBuiltInDisplayCutout">true</bool>
+    <!-- If true, and there is a cutout on the secondary built in display, the cutout will be masked
+         by shrinking the display such that it does not overlap the cutout area. -->
+    <bool name="config_maskSecondaryBuiltInDisplayCutout">true</bool>
 
     <!-- Height of the status bar -->
     <dimen name="status_bar_height_portrait">28dp</dimen>
diff --git a/proto/src/criticalevents/critical_event_log.proto b/proto/src/criticalevents/critical_event_log.proto
index 9cda267..cffcd09 100644
--- a/proto/src/criticalevents/critical_event_log.proto
+++ b/proto/src/criticalevents/critical_event_log.proto
@@ -60,8 +60,11 @@
     JavaCrash java_crash = 5;
     NativeCrash native_crash = 6;
     SystemServerStarted system_server_started = 7;
+    InstallPackages install_packages = 8;
   }
 
+  message InstallPackages {}
+
   message SystemServerStarted {}
 
   message Watchdog {
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index a6ed846..4b3772a 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -284,7 +284,7 @@
         final AssociationInfo association = new AssociationInfo(id, userId, packageName,
                 /* tag */ null, macAddress, displayName, deviceProfile, associatedDevice,
                 selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false,
-                timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0);
+                /* pending */ false, timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0);
 
         // Add role holder for association (if specified) and add new association to store.
         maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index a7dbd1c..e4cc1f8 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -18,6 +18,8 @@
 
 import static android.os.UserHandle.getCallingUserId;
 
+import static com.android.server.companion.CompanionDeviceManagerService.PerUserAssociationSet;
+
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
@@ -26,9 +28,11 @@
 import android.companion.datatransfer.SystemDataTransferRequest;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManagerInternal;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.CollectionUtils;
 import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
 
@@ -58,6 +62,14 @@
     @NonNull
     private final AssociationRequestsProcessor mAssociationRequestsProcessor;
 
+    /**
+     * A structure that consists of a set of restored associations that are pending corresponding
+     * companion app to be installed.
+     */
+    @GuardedBy("mAssociationsPendingAppInstall")
+    private final PerUserAssociationSet mAssociationsPendingAppInstall =
+            new PerUserAssociationSet();
+
     BackupRestoreProcessor(@NonNull CompanionDeviceManagerService service,
                            @NonNull AssociationStoreImpl associationStore,
                            @NonNull PersistentDataStore persistentStore,
@@ -124,7 +136,7 @@
         byte[] requestsPayload = new byte[buffer.getInt()];
         buffer.get(requestsPayload);
         List<SystemDataTransferRequest> restoredRequestsForUser =
-                mSystemDataTransferRequestStore.readRequestsFromPayload(requestsPayload);
+                mSystemDataTransferRequestStore.readRequestsFromPayload(requestsPayload, userId);
 
         // Get a list of installed packages ahead of time.
         List<ApplicationInfo> installedApps = mPackageManager.getInstalledApplications(
@@ -170,7 +182,7 @@
                 mAssociationRequestsProcessor.maybeGrantRoleAndStoreAssociation(newAssociation,
                         null, null);
             } else {
-                // TODO(b/314992577): Check if package is installed before granting
+                addToPendingAppInstall(newAssociation);
             }
 
             // Re-map restored system data transfer requests to newly created associations
@@ -185,6 +197,30 @@
         mService.persistStateForUser(userId);
     }
 
+    void addToPendingAppInstall(@NonNull AssociationInfo association) {
+        association = (new AssociationInfo.Builder(association))
+                .setPending(true)
+                .build();
+
+        synchronized (mAssociationsPendingAppInstall) {
+            mAssociationsPendingAppInstall.forUser(association.getUserId()).add(association);
+        }
+    }
+
+    void removeFromPendingAppInstall(@NonNull AssociationInfo association) {
+        synchronized (mAssociationsPendingAppInstall) {
+            mAssociationsPendingAppInstall.forUser(association.getUserId()).remove(association);
+        }
+    }
+
+    @NonNull
+    Set<AssociationInfo> getAssociationsPendingAppInstallForUser(@UserIdInt int userId) {
+        synchronized (mAssociationsPendingAppInstall) {
+            // Return a copy.
+            return new ArraySet<>(mAssociationsPendingAppInstall.forUser(userId));
+        }
+    }
+
     /**
      * Detects and handles collision between restored association and local association. Returns
      * true if there has been a collision and false otherwise.
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 858887a..056ec89 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -287,7 +287,9 @@
         final Set<Integer> usersToPersistStateFor = new ArraySet<>();
 
         for (AssociationInfo association : allAssociations) {
-            if (!association.isRevoked()) {
+            if (association.isPending()) {
+                mBackupRestoreProcessor.addToPendingAppInstall(association);
+            } else if (!association.isRevoked()) {
                 activeAssociations.add(association);
             } else if (maybeRemoveRoleHolderForAssociation(association)) {
                 // Nothing more to do here, but we'll need to persist all the associations to the
@@ -514,6 +516,9 @@
                 mAssociationStore.getAssociationsForUser(userId));
         // ... and add the revoked (removed) association, that are yet to be permanently removed.
         allAssociations.addAll(getPendingRoleHolderRemovalAssociationsForUser(userId));
+        // ... and add the restored associations that are pending missing package installation.
+        allAssociations.addAll(mBackupRestoreProcessor
+                .getAssociationsPendingAppInstallForUser(userId));
 
         final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
 
@@ -583,7 +588,19 @@
 
     private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) {
         if (DEBUG) Log.i(TAG, "onPackageAddedInternal() u" + userId + "/" + packageName);
-        // TODO(b/314992577): Retroactively grant roles for restored associations
+
+        Set<AssociationInfo> associationsPendingAppInstall = mBackupRestoreProcessor
+                .getAssociationsPendingAppInstallForUser(userId);
+        for (AssociationInfo association : associationsPendingAppInstall) {
+            if (!packageName.equals(association.getPackageName())) continue;
+
+            AssociationInfo newAssociation = new AssociationInfo.Builder(association)
+                    .setPending(false)
+                    .build();
+            mAssociationRequestsProcessor.maybeGrantRoleAndStoreAssociation(newAssociation,
+                    null, null);
+            mBackupRestoreProcessor.removeFromPendingAppInstall(association);
+        }
     }
 
     // Revoke associations if the selfManaged companion device does not connect for 3 months.
@@ -1152,6 +1169,15 @@
                 usedIds.put(it.getId(), true);
             }
 
+            // Some IDs may be reserved by associations that aren't stored yet due to missing
+            // package after a backup restoration. We don't want the ID to have been taken by
+            // another association by the time when it is activated from the package installation.
+            final Set<AssociationInfo> pendingAssociations = mBackupRestoreProcessor
+                    .getAssociationsPendingAppInstallForUser(userId);
+            for (AssociationInfo it: pendingAssociations) {
+                usedIds.put(it.getId(), true);
+            }
+
             // Second: collect all IDs that have been previously used for this package (and user).
             final Set<Integer> previouslyUsedIds =
                     getPreviouslyUsedIdsForPackageLocked(userId, packageName);
@@ -1718,7 +1744,7 @@
         }
     }
 
-    private static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
+    static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
         @Override
         protected @NonNull Set<AssociationInfo> create(int userId) {
             return new ArraySet<>();
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index dbaf7e8..1ebe65c 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -189,6 +189,7 @@
     private static final String XML_ATTR_SELF_MANAGED = "self_managed";
     private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby";
     private static final String XML_ATTR_REVOKED = "revoked";
+    private static final String XML_ATTR_PENDING = "pending";
     private static final String XML_ATTR_TIME_APPROVED = "time_approved";
     private static final String XML_ATTR_LAST_TIME_CONNECTED = "last_time_connected";
     private static final String XML_ATTR_SYSTEM_DATA_SYNC_FLAGS = "system_data_sync_flags";
@@ -464,8 +465,8 @@
 
         out.add(new AssociationInfo(associationId, userId, appPackage, tag,
                 MacAddress.fromString(deviceAddress), null, profile, null,
-                /* managedByCompanionApp */ false, notify, /* revoked */ false, timeApproved,
-                Long.MAX_VALUE, /* systemDataSyncFlags */ 0));
+                /* managedByCompanionApp */ false, notify, /* revoked */ false, /* pending */ false,
+                timeApproved, Long.MAX_VALUE, /* systemDataSyncFlags */ 0));
     }
 
     private static void readAssociationsV1(@NonNull TypedXmlPullParser parser,
@@ -496,6 +497,7 @@
         final boolean selfManaged = readBooleanAttribute(parser, XML_ATTR_SELF_MANAGED);
         final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
         final boolean revoked = readBooleanAttribute(parser, XML_ATTR_REVOKED, false);
+        final boolean pending = readBooleanAttribute(parser, XML_ATTR_PENDING, false);
         final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
         final long lastTimeConnected = readLongAttribute(
                 parser, XML_ATTR_LAST_TIME_CONNECTED, Long.MAX_VALUE);
@@ -504,7 +506,7 @@
 
         final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId,
                 appPackage, tag, macAddress, displayName, profile, selfManaged, notify, revoked,
-                timeApproved, lastTimeConnected, systemDataSyncFlags);
+                pending, timeApproved, lastTimeConnected, systemDataSyncFlags);
         if (associationInfo != null) {
             out.add(associationInfo);
         }
@@ -558,8 +560,8 @@
         writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged());
         writeBooleanAttribute(
                 serializer, XML_ATTR_NOTIFY_DEVICE_NEARBY, a.isNotifyOnDeviceNearby());
-        writeBooleanAttribute(
-                serializer, XML_ATTR_REVOKED, a.isRevoked());
+        writeBooleanAttribute(serializer, XML_ATTR_REVOKED, a.isRevoked());
+        writeBooleanAttribute(serializer, XML_ATTR_PENDING, a.isPending());
         writeLongAttribute(serializer, XML_ATTR_TIME_APPROVED, a.getTimeApprovedMs());
         writeLongAttribute(
                 serializer, XML_ATTR_LAST_TIME_CONNECTED, a.getLastTimeConnectedMs());
@@ -603,14 +605,14 @@
             @UserIdInt int userId, @NonNull String appPackage, @Nullable String tag,
             @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
             @Nullable String profile, boolean selfManaged, boolean notify, boolean revoked,
-            long timeApproved, long lastTimeConnected, int systemDataSyncFlags) {
+            boolean pending, long timeApproved, long lastTimeConnected, int systemDataSyncFlags) {
         AssociationInfo associationInfo = null;
         try {
             // We do not persist AssociatedDevice, which means that AssociationInfo retrieved from
             // datastore is not guaranteed to be identical to the one from initial association.
             associationInfo = new AssociationInfo(associationId, userId, appPackage, tag,
                     macAddress, displayName, profile, null, selfManaged, notify,
-                    revoked, timeApproved, lastTimeConnected, systemDataSyncFlags);
+                    revoked, pending, timeApproved, lastTimeConnected, systemDataSyncFlags);
         } catch (Exception e) {
             if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e);
         }
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
index 8fe0454..51c5fd6 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
@@ -69,7 +69,6 @@
  *   <request
  *     association_id="1"
  *     data_type="1"
- *     user_id="12"
  *     is_user_consented="true"
  *   </request>
  * </requests>
@@ -86,7 +85,6 @@
 
     private static final String XML_ATTR_ASSOCIATION_ID = "association_id";
     private static final String XML_ATTR_DATA_TYPE = "data_type";
-    private static final String XML_ATTR_USER_ID = "user_id";
     private static final String XML_ATTR_IS_USER_CONSENTED = "is_user_consented";
 
     private static final int READ_FROM_DISK_TIMEOUT = 5; // in seconds
@@ -169,12 +167,12 @@
      * Parse the byte array containing XML information of system data transfer requests into
      * an array list of requests.
      */
-    public List<SystemDataTransferRequest> readRequestsFromPayload(byte[] payload) {
+    public List<SystemDataTransferRequest> readRequestsFromPayload(byte[] payload, int userId) {
         try (ByteArrayInputStream in = new ByteArrayInputStream(payload)) {
             final TypedXmlPullParser parser = Xml.resolvePullParser(in);
             XmlUtils.beginDocument(parser, XML_TAG_REQUESTS);
 
-            return readRequestsFromXml(parser);
+            return readRequestsFromXml(parser, userId);
         } catch (XmlPullParserException | IOException e) {
             Slog.e(LOG_TAG, "Error while reading requests file", e);
             return new ArrayList<>();
@@ -226,7 +224,7 @@
                 final TypedXmlPullParser parser = Xml.resolvePullParser(in);
                 XmlUtils.beginDocument(parser, XML_TAG_REQUESTS);
 
-                return readRequestsFromXml(parser);
+                return readRequestsFromXml(parser, userId);
             } catch (XmlPullParserException | IOException e) {
                 Slog.e(LOG_TAG, "Error while reading requests file", e);
                 return new ArrayList<>();
@@ -236,7 +234,8 @@
 
     @NonNull
     private ArrayList<SystemDataTransferRequest> readRequestsFromXml(
-            @NonNull TypedXmlPullParser parser) throws XmlPullParserException, IOException {
+            @NonNull TypedXmlPullParser parser, int userId)
+            throws XmlPullParserException, IOException {
         if (!isStartOfTag(parser, XML_TAG_REQUESTS)) {
             throw new XmlPullParserException("The XML doesn't have start tag: " + XML_TAG_REQUESTS);
         }
@@ -249,14 +248,15 @@
                 break;
             }
             if (isStartOfTag(parser, XML_TAG_REQUEST)) {
-                requests.add(readRequestFromXml(parser));
+                requests.add(readRequestFromXml(parser, userId));
             }
         }
 
         return requests;
     }
 
-    private SystemDataTransferRequest readRequestFromXml(@NonNull TypedXmlPullParser parser)
+    private SystemDataTransferRequest readRequestFromXml(@NonNull TypedXmlPullParser parser,
+            int userId)
             throws XmlPullParserException, IOException {
         if (!isStartOfTag(parser, XML_TAG_REQUEST)) {
             throw new XmlPullParserException("XML doesn't have start tag: " + XML_TAG_REQUEST);
@@ -264,7 +264,6 @@
 
         final int associationId = readIntAttribute(parser, XML_ATTR_ASSOCIATION_ID);
         final int dataType = readIntAttribute(parser, XML_ATTR_DATA_TYPE);
-        final int userId = readIntAttribute(parser, XML_ATTR_USER_ID);
         final boolean isUserConsented = readBooleanAttribute(parser, XML_ATTR_IS_USER_CONSENTED);
 
         switch (dataType) {
@@ -321,7 +320,6 @@
 
         writeIntAttribute(serializer, XML_ATTR_ASSOCIATION_ID, request.getAssociationId());
         writeIntAttribute(serializer, XML_ATTR_DATA_TYPE, request.getDataType());
-        writeIntAttribute(serializer, XML_ATTR_USER_ID, request.getUserId());
         writeBooleanAttribute(serializer, XML_ATTR_IS_USER_CONSENTED, request.isUserConsented());
 
         serializer.endTag(null, XML_TAG_REQUEST);
diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
index d089b05..2f9b6a5 100644
--- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
+++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java
@@ -55,9 +55,7 @@
     @GuardedBy("mCameras")
     private final Map<IBinder, CameraDescriptor> mCameras = new ArrayMap<>();
 
-    public VirtualCameraController() {
-        connectVirtualCameraService();
-    }
+    public VirtualCameraController() {}
 
     @VisibleForTesting
     VirtualCameraController(IVirtualCameraService virtualCameraService) {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index dd001ec..a3fc3bf 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -203,6 +203,7 @@
         "com_android_wm_shell_flags_lib",
         "com.android.server.utils_aconfig-java",
         "service-jobscheduler-deviceidle.flags-aconfig-java",
+        "policy_flags_lib",
     ],
     javac_shard_size: 50,
     javacflags: [
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 9f279b1..329aac6 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -48,6 +48,8 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.am.DropboxRateLimiter;
+import com.android.server.os.TombstoneProtos;
+import com.android.server.os.TombstoneProtos.Tombstone;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -60,11 +62,14 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.attribute.PosixFilePermissions;
+import java.util.AbstractMap;
 import java.util.HashMap;
 import java.util.Iterator;
+import java.util.Map;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 /**
  * Performs a number of miscellaneous, non-system-critical actions
@@ -332,12 +337,12 @@
      *
      * @param ctx Context
      * @param tombstone path to the tombstone
-     * @param proto whether the tombstone is stored as proto
+     * @param tombstoneProto the parsed proto tombstone
      * @param processName the name of the process corresponding to the tombstone
      * @param tmpFileLock the lock for reading/writing tmp files
      */
     public static void addTombstoneToDropBox(
-                Context ctx, File tombstone, boolean proto, String processName,
+                Context ctx, File tombstone, Tombstone tombstoneProto, String processName,
                 ReentrantLock tmpFileLock) {
         final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
         if (db == null) {
@@ -347,31 +352,33 @@
 
         // Check if we should rate limit and abort early if needed.
         DropboxRateLimiter.RateLimitResult rateLimitResult =
-                sDropboxRateLimiter.shouldRateLimit(
-                        proto ? TAG_TOMBSTONE_PROTO_WITH_HEADERS : TAG_TOMBSTONE, processName);
+                sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName);
         if (rateLimitResult.shouldRateLimit()) return;
 
         HashMap<String, Long> timestamps = readTimestamps();
         try {
-            if (proto) {
-                if (recordFileTimestamp(tombstone, timestamps)) {
-                    // We need to attach the count indicating the number of dropped dropbox entries
-                    // due to rate limiting. Do this by enclosing the proto tombsstone in a
-                    // container proto that has the dropped entry count and the proto tombstone as
-                    // bytes (to avoid the complexity of reading and writing nested protos).
-                    tmpFileLock.lock();
-                    try {
-                        addAugmentedProtoToDropbox(tombstone, db, rateLimitResult);
-                    } finally {
-                        tmpFileLock.unlock();
-                    }
+            // Remove the memory data from the proto.
+            Tombstone tombstoneProtoWithoutMemory = removeMemoryFromTombstone(tombstoneProto);
+
+            final byte[] tombstoneBytes = tombstoneProtoWithoutMemory.toByteArray();
+
+            // Use JNI to call the c++ proto to text converter and add the headers to the tombstone.
+            String tombstoneWithoutMemory = new StringBuilder(getBootHeadersToLogAndUpdate())
+                    .append(rateLimitResult.createHeader())
+                    .append(getTombstoneText(tombstoneBytes))
+                    .toString();
+
+            // Add the tombstone without memory data to dropbox.
+            db.addText(TAG_TOMBSTONE, tombstoneWithoutMemory);
+
+            // Add the tombstone proto to dropbox.
+            if (recordFileTimestamp(tombstone, timestamps)) {
+                tmpFileLock.lock();
+                try {
+                    addAugmentedProtoToDropbox(tombstone, tombstoneBytes, db, rateLimitResult);
+                } finally {
+                    tmpFileLock.unlock();
                 }
-            } else {
-                // Add the header indicating how many events have been dropped due to rate limiting.
-                final String headers = getBootHeadersToLogAndUpdate()
-                        + rateLimitResult.createHeader();
-                addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
-                                 TAG_TOMBSTONE);
             }
         } catch (IOException e) {
             Slog.e(TAG, "Can't log tombstone", e);
@@ -380,11 +387,8 @@
     }
 
     private static void addAugmentedProtoToDropbox(
-                File tombstone, DropBoxManager db,
+                File tombstone, byte[] tombstoneBytes, DropBoxManager db,
                 DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException {
-        // Read the proto tombstone file as bytes.
-        final byte[] tombstoneBytes = Files.readAllBytes(tombstone.toPath());
-
         final File tombstoneProtoWithHeaders = File.createTempFile(
                 tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR);
         Files.setPosixFilePermissions(
@@ -417,6 +421,8 @@
         }
     }
 
+    private static native String getTombstoneText(byte[] tombstoneBytes);
+
     private static void addLastkToDropBox(
             DropBoxManager db, HashMap<String, Long> timestamps,
             String headers, String footers, String filename, int maxSize,
@@ -434,6 +440,31 @@
         addFileWithFootersToDropBox(db, timestamps, headers, footers, filename, maxSize, tag);
     }
 
+    /** Removes memory information from the Tombstone proto. */
+    @VisibleForTesting
+    public static Tombstone removeMemoryFromTombstone(Tombstone tombstoneProto) {
+        Tombstone.Builder tombstoneBuilder = tombstoneProto.toBuilder()
+                .clearMemoryMappings()
+                .clearThreads()
+                .putAllThreads(tombstoneProto.getThreadsMap().entrySet()
+                        .stream()
+                        .map(BootReceiver::clearMemoryDump)
+                        .collect(Collectors.toMap(e->e.getKey(), e->e.getValue())));
+
+        if (tombstoneProto.hasSignalInfo()) {
+            tombstoneBuilder.setSignalInfo(
+                    tombstoneProto.getSignalInfo().toBuilder().clearFaultAdjacentMetadata());
+        }
+
+        return tombstoneBuilder.build();
+    }
+
+    private static AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread> clearMemoryDump(
+            Map.Entry<Integer, TombstoneProtos.Thread> e) {
+        return new AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread>(
+            e.getKey(), e.getValue().toBuilder().clearMemoryDump().build());
+    }
+
     private static void addFileToDropBox(
             DropBoxManager db, HashMap<String, Long> timestamps,
             String headers, String filename, int maxSize, String tag) throws IOException {
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index e289a56..e923e30a 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -43,3 +43,6 @@
 per-file TelephonyRegistry.java = file:/telephony/OWNERS
 per-file UiModeManagerService.java = file:/packages/SystemUI/OWNERS
 per-file VcnManagementService.java = file:/services/core/java/com/android/server/vcn/OWNERS
+
+# SystemConfig
+per-file SystemConfig.java = file:/PACKAGE_MANAGER_OWNERS
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
new file mode 100644
index 0000000..70bd4b3
--- /dev/null
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.projection.MediaProjectionInfo;
+import android.media.projection.MediaProjectionManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
+import com.android.server.wm.WindowManagerInternal;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Service that monitors for notifications with sensitive content and protects content from screen
+ * sharing
+ */
+public final class SensitiveContentProtectionManagerService extends SystemService {
+    private static final String TAG = "SensitiveContentProtect";
+    private static final boolean DEBUG = false;
+
+    @VisibleForTesting
+    NotificationListener mNotificationListener;
+    private @Nullable MediaProjectionManager mProjectionManager;
+    private @Nullable WindowManagerInternal mWindowManager;
+
+    private final MediaProjectionManager.Callback mProjectionCallback =
+            new MediaProjectionManager.Callback() {
+                @Override
+                public void onStart(MediaProjectionInfo info) {
+                    if (DEBUG) Log.d(TAG, "onStart projection: " + info);
+                    onProjectionStart();
+                }
+
+                @Override
+                public void onStop(MediaProjectionInfo info) {
+                    if (DEBUG) Log.d(TAG, "onStop projection: " + info);
+                    onProjectionEnd();
+                }
+            };
+
+    public SensitiveContentProtectionManagerService(@NonNull Context context) {
+        super(context);
+        mNotificationListener = new NotificationListener();
+    }
+
+    @Override
+    public void onStart() {}
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase != SystemService.PHASE_BOOT_COMPLETED) {
+            return;
+        }
+
+        if (DEBUG) Log.d(TAG, "onBootPhase - PHASE_BOOT_COMPLETED");
+
+        init(getContext().getSystemService(MediaProjectionManager.class),
+                LocalServices.getService(WindowManagerInternal.class));
+    }
+
+    @VisibleForTesting
+    void init(MediaProjectionManager projectionManager,
+            WindowManagerInternal windowManager) {
+        if (DEBUG) Log.d(TAG, "init");
+
+        checkNotNull(projectionManager, "Failed to get valid MediaProjectionManager");
+        checkNotNull(windowManager, "Failed to get valid WindowManagerInternal");
+
+        mProjectionManager = projectionManager;
+        mWindowManager = windowManager;
+
+        // TODO(b/317250444): use MediaProjectionManagerService directly, reduces unnecessary
+        //  handler, delegate, and binder death recipient
+        mProjectionManager.addCallback(mProjectionCallback, new Handler(Looper.getMainLooper()));
+
+        try {
+            mNotificationListener.registerAsSystemService(getContext(),
+                    new ComponentName(getContext(), NotificationListener.class),
+                    UserHandle.USER_ALL);
+        } catch (RemoteException e) {
+            // Intra-process call, should never happen.
+        }
+    }
+
+    /** Cleanup any callbacks and listeners */
+    @VisibleForTesting
+    void onDestroy() {
+        if (mProjectionManager != null) {
+            mProjectionManager.removeCallback(mProjectionCallback);
+        }
+
+        try {
+            mNotificationListener.unregisterAsSystemService();
+        } catch (RemoteException e) {
+            // Intra-process call, should never happen.
+        }
+
+        if (mWindowManager != null) {
+            onProjectionEnd();
+        }
+    }
+
+    private void onProjectionStart() {
+        StatusBarNotification[] notifications;
+        try {
+            notifications = mNotificationListener.getActiveNotifications();
+        } catch (SecurityException e) {
+            Log.e(TAG, "SensitiveContentProtectionManagerService doesn't have access.", e);
+            notifications = new StatusBarNotification[0];
+        }
+
+        RankingMap rankingMap;
+        try {
+            rankingMap = mNotificationListener.getCurrentRanking();
+        } catch (SecurityException e) {
+            Log.e(TAG, "SensitiveContentProtectionManagerService doesn't have access.", e);
+            rankingMap = null;
+        }
+
+        // notify windowmanager of any currently posted sensitive content notifications
+        Set<PackageInfo> packageInfos = getSensitivePackagesFromNotifications(
+                notifications,
+                rankingMap);
+
+        mWindowManager.setShouldBlockScreenCaptureForApp(packageInfos);
+    }
+
+    private void onProjectionEnd() {
+        // notify windowmanager to clear any sensitive notifications observed during projection
+        // session
+        mWindowManager.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+    }
+
+    private Set<PackageInfo> getSensitivePackagesFromNotifications(
+            StatusBarNotification[] notifications, RankingMap rankingMap) {
+        if (rankingMap == null) {
+            Log.w(TAG, "Ranking map not initialized.");
+            return Collections.emptySet();
+        }
+
+        Set<PackageInfo> sensitivePackages = new ArraySet<>();
+        for (StatusBarNotification sbn : notifications) {
+            NotificationListenerService.Ranking ranking =
+                    rankingMap.getRawRankingObject(sbn.getKey());
+            if (ranking != null && ranking.hasSensitiveContent()) {
+                PackageInfo info = new PackageInfo(sbn.getPackageName(), sbn.getUid());
+                sensitivePackages.add(info);
+            }
+        }
+        return sensitivePackages;
+    }
+
+    // TODO(b/317251408): add trigger that updates on onNotificationPosted,
+    //  onNotificationRankingUpdate and onListenerConnected
+    @VisibleForTesting
+    static class NotificationListener extends NotificationListenerService {}
+}
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 40b29d7..3483c1a 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -315,6 +315,11 @@
     private final ArraySet<String> mBugreportWhitelistedPackages = new ArraySet<>();
     private final ArraySet<String> mAppDataIsolationWhitelistedApps = new ArraySet<>();
 
+    // These packages will be set as 'prevent disable', where they are no longer possible
+    // for the end user to disable via settings. This flag should only be used for packages
+    // which meet the 'force or keep enabled apps' policy.
+    private final ArrayList<String> mPreventUserDisablePackages = new ArrayList<>();
+
     // Map of packagesNames to userTypes. Stored temporarily until cleared by UserManagerService().
     private ArrayMap<String, Set<String>> mPackageToUserTypeWhitelist = new ArrayMap<>();
     private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>();
@@ -504,6 +509,10 @@
         return mAppDataIsolationWhitelistedApps;
     }
 
+    public @NonNull ArrayList<String> getPreventUserDisablePackages() {
+        return mPreventUserDisablePackages;
+    }
+
     /**
      * Gets map of packagesNames to userTypes, dictating on which user types each package should be
      * initially installed, and then removes this map from SystemConfig.
@@ -1309,6 +1318,16 @@
                         }
                         XmlUtils.skipCurrentTag(parser);
                     } break;
+                    case "prevent-disable": {
+                        String pkgname = parser.getAttributeValue(null, "package");
+                        if (pkgname == null) {
+                            Slog.w(TAG, "<" + name + "> without package in " + permFile
+                                    + " at " + parser.getPositionDescription());
+                        } else {
+                            mPreventUserDisablePackages.add(pkgname);
+                        }
+                        XmlUtils.skipCurrentTag(parser);
+                    } break;
                     case "install-in-user-type": {
                         // NB: We allow any directory permission to declare install-in-user-type.
                         readInstallInUserType(parser,
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index e7e721b..9db5d0a 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -176,6 +176,7 @@
         "system_performance",
         "system_sw_touch",
         "system_sw_usb",
+        "statsd",
         "test_suites",
         "text",
         "threadnetwork",
diff --git a/services/core/java/com/android/server/criticalevents/CriticalEventLog.java b/services/core/java/com/android/server/criticalevents/CriticalEventLog.java
index 0814375..816c349 100644
--- a/services/core/java/com/android/server/criticalevents/CriticalEventLog.java
+++ b/services/core/java/com/android/server/criticalevents/CriticalEventLog.java
@@ -29,6 +29,7 @@
 import com.android.server.criticalevents.nano.CriticalEventProto;
 import com.android.server.criticalevents.nano.CriticalEventProto.AppNotResponding;
 import com.android.server.criticalevents.nano.CriticalEventProto.HalfWatchdog;
+import com.android.server.criticalevents.nano.CriticalEventProto.InstallPackages;
 import com.android.server.criticalevents.nano.CriticalEventProto.JavaCrash;
 import com.android.server.criticalevents.nano.CriticalEventProto.NativeCrash;
 import com.android.server.criticalevents.nano.CriticalEventProto.SystemServerStarted;
@@ -142,6 +143,13 @@
         return System.currentTimeMillis();
     }
 
+    /** Logs when one or more packages are installed. */
+    public void logInstallPackagesStarted() {
+        CriticalEventProto event = new CriticalEventProto();
+        event.setInstallPackages(new InstallPackages());
+        log(event);
+    }
+
     /** Logs when system server started. */
     public void logSystemServerStarted() {
         CriticalEventProto event = new CriticalEventProto();
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 07b333a..393e7ef 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -198,7 +198,12 @@
                 mIsConnected = true;
                 service = mService;
             }
-            service.onSessionActiveStateChanged(MediaSession2Record.this);
+
+            // TODO (b/318745416): Add support for FGS in MediaSession2. Passing a
+            // null playback state means the owning process will not be allowed to
+            // run in the foreground.
+            service.onSessionActiveStateChanged(MediaSession2Record.this,
+                    /* playbackState= */ null);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index cce66e2..53f780e 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -1144,7 +1144,7 @@
             mIsActive = active;
             long token = Binder.clearCallingIdentity();
             try {
-                mService.onSessionActiveStateChanged(MediaSessionRecord.this);
+                mService.onSessionActiveStateChanged(MediaSessionRecord.this, mPlaybackState);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 2affdfc..2cd3ab1 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -260,7 +260,8 @@
         return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
     }
 
-    void onSessionActiveStateChanged(MediaSessionRecordImpl record) {
+    void onSessionActiveStateChanged(
+            MediaSessionRecordImpl record, @Nullable PlaybackState playbackState) {
         synchronized (mLock) {
             FullUserRecord user = getFullUserRecordLocked(record.getUserId());
             if (user == null) {
@@ -287,7 +288,9 @@
                 user.mPriorityStack.onSessionActiveStateChanged(record);
             }
             setForegroundServiceAllowance(
-                    record, /* allowRunningInForeground= */ record.isActive());
+                    record,
+                    /* allowRunningInForeground= */ record.isActive()
+                            && (playbackState == null || playbackState.isActive()));
             mHandler.postSessionsChanged(record);
         }
     }
@@ -386,7 +389,9 @@
             user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
             if (playbackState != null) {
                 setForegroundServiceAllowance(
-                        record, playbackState.shouldAllowServiceToRunInForeground());
+                        record,
+                        /* allowRunningInForeground= */ playbackState.isActive()
+                                && record.isActive());
             }
         }
     }
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 6b7db2d..a6f71c2 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -94,8 +94,6 @@
 
     private static final float DEFAULT_VOLUME = 1.0f;
     // TODO (b/291899544): remove for release
-    private static final String POLITE_STRATEGY1 = "rule1";
-    private static final String POLITE_STRATEGY2 = "rule2";
     private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED = 1;
     private static final int DEFAULT_NOTIFICATION_COOLDOWN_ENABLED_FOR_WORK = 0;
     private static final int DEFAULT_NOTIFICATION_COOLDOWN_ALL = 1;
@@ -146,7 +144,6 @@
     private boolean mNotificationCooldownApplyToAll;
     private boolean mNotificationCooldownVibrateUnlocked;
 
-    private boolean mEnablePoliteNotificationsFeature;
     private final PolitenessStrategy mStrategy;
     private int mCurrentWorkProfileId = UserHandle.USER_NULL;
 
@@ -192,9 +189,7 @@
                 .build();
         mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume);
 
-        mEnablePoliteNotificationsFeature = Flags.politeNotifications();
-
-        if (mEnablePoliteNotificationsFeature) {
+        if (Flags.politeNotifications()) {
             mStrategy = getPolitenessStrategy();
         } else {
             mStrategy = null;
@@ -205,21 +200,23 @@
     }
 
     private PolitenessStrategy getPolitenessStrategy() {
-        final String politenessStrategy = mFlagResolver.getStringValue(
-                NotificationFlags.NOTIF_COOLDOWN_RULE);
-
-        if (POLITE_STRATEGY2.equals(politenessStrategy)) {
-            return new Strategy2(mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
+        if (Flags.crossAppPoliteNotifications()) {
+            PolitenessStrategy appStrategy = new StrategyPerApp(
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
-                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2));
-        } else {
-            if (!POLITE_STRATEGY1.equals(politenessStrategy)) {
-                Log.w(TAG, "Invalid cooldown strategy: " + politenessStrategy + ". Defaulting to "
-                        + POLITE_STRATEGY1);
-            }
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET));
 
-            return new Strategy1(mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
+            return new StrategyGlobal(
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
+                    appStrategy);
+        } else {
+            return new StrategyPerApp(
+                    mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1),
                     mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2),
@@ -266,7 +263,7 @@
         mContext.getContentResolver().registerContentObserver(
                 SettingsObserver.NOTIFICATION_LIGHT_PULSE_URI, false, mSettingsObserver,
                 UserHandle.USER_ALL);
-        if (mEnablePoliteNotificationsFeature) {
+        if (Flags.politeNotifications()) {
             mContext.getContentResolver().registerContentObserver(
                     SettingsObserver.NOTIFICATION_COOLDOWN_ENABLED_URI, false, mSettingsObserver,
                     UserHandle.USER_ALL);
@@ -280,7 +277,7 @@
     }
 
     private void loadUserSettings() {
-        if (mEnablePoliteNotificationsFeature) {
+        if (Flags.politeNotifications()) {
             try {
                 mCurrentWorkProfileId = getManagedProfileId(ActivityManager.getCurrentUser());
 
@@ -301,11 +298,14 @@
                     mContext.getContentResolver(),
                     Settings.System.NOTIFICATION_COOLDOWN_ALL, DEFAULT_NOTIFICATION_COOLDOWN_ALL,
                     UserHandle.USER_CURRENT) != 0;
-                mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
-                    mContext.getContentResolver(),
-                    Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
-                    DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
-                    UserHandle.USER_CURRENT) != 0;
+                mStrategy.setApplyCooldownPerPackage(mNotificationCooldownApplyToAll);
+                if (Flags.vibrateWhileUnlocked()) {
+                    mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
+                        mContext.getContentResolver(),
+                        Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
+                        DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
+                        UserHandle.USER_CURRENT) != 0;
+                }
             } catch (Exception e) {
                 Log.e(TAG, "Failed to read Settings: " + e);
             }
@@ -482,10 +482,10 @@
                     getPolitenessState(record));
         }
         record.setAudiblyAlerted(buzz || beep);
-        if (mEnablePoliteNotificationsFeature) {
+        if (Flags.politeNotifications()) {
             // Update last alert time
             if (buzz || beep) {
-                record.getChannel().setLastNotificationUpdateTimeMs(System.currentTimeMillis());
+                mStrategy.setLastNotificationUpdateTimeMs(record, System.currentTimeMillis());
             }
         }
         return buzzBeepBlinkLoggingCode;
@@ -618,7 +618,7 @@
 
     private boolean isPoliteNotificationFeatureEnabled(final NotificationRecord record) {
         // Check feature flag
-        if (!mEnablePoliteNotificationsFeature) {
+        if (!Flags.politeNotifications()) {
             return false;
         }
 
@@ -1064,9 +1064,13 @@
         // Volume for muted state
         protected final float mVolumeMuted;
 
+        protected boolean mApplyPerPackage;
+        protected final Map<String, Long> mLastUpdatedTimestampByPackage;
+
         public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite,
                 int volumeMuted) {
             mVolumeStates = new HashMap<>();
+            mLastUpdatedTimestampByPackage = new HashMap<>();
 
             this.mTimeoutPolite = timeoutPolite;
             this.mTimeoutMuted = timeoutMuted;
@@ -1076,10 +1080,38 @@
 
         abstract void onNotificationPosted(NotificationRecord record);
 
+        /**
+         *  Set true if the cooldown strategy should apply per app(package).
+         *  Otherwise apply per conversation channel.
+         * @param applyPerPackage if the cooldown should be applied per app
+         */
+        void setApplyCooldownPerPackage(boolean applyPerPackage) {
+            mApplyPerPackage = applyPerPackage;
+        }
+
+        boolean shouldIgnoreNotification(final NotificationRecord record) {
+            // Ignore group summaries
+            return (record.getSbn().isGroup() && record.getSbn().getNotification()
+                    .isGroupSummary());
+        }
+
+        /**
+         * Get the key that determines the grouping for the cooldown behavior.
+         *
+         * @param record the notification being posted
+         * @return the key to group this notification under
+         */
         String getChannelKey(final NotificationRecord record) {
-            // use conversationId if it's a conversation
+            // Use conversationId if it's a conversation
             String channelId = record.getChannel().getConversationId() != null
                     ? record.getChannel().getConversationId() : record.getChannel().getId();
+
+            // Use only the package name to apply cooldown per app, unless the user explicitly
+            // changed the channel notification sound => treat separately
+            if (mApplyPerPackage && !record.getChannel().hasUserSetSound()) {
+                channelId = "";
+            }
+
             return record.getSbn().getNormalizedUserId() + ":" + record.getSbn().getPackageName()
                     + ":" + channelId;
         }
@@ -1121,12 +1153,59 @@
             final String key = getChannelKey(record);
             // reset to default state after user interaction
             mVolumeStates.put(key, POLITE_STATE_DEFAULT);
-            record.getChannel().setLastNotificationUpdateTimeMs(0);
+            setLastNotificationUpdateTimeMs(record, 0);
         }
 
         public final @PolitenessState int getPolitenessState(final NotificationRecord record) {
             return mVolumeStates.getOrDefault(getChannelKey(record), POLITE_STATE_DEFAULT);
         }
+
+        void setLastNotificationUpdateTimeMs(final NotificationRecord record,
+                long timestampMillis) {
+            record.getChannel().setLastNotificationUpdateTimeMs(timestampMillis);
+            mLastUpdatedTimestampByPackage.put(record.getSbn().getPackageName(), timestampMillis);
+        }
+
+        long getLastNotificationUpdateTimeMs(final NotificationRecord record) {
+            if (record.getChannel().hasUserSetSound() || !mApplyPerPackage) {
+                return record.getChannel().getLastNotificationUpdateTimeMs();
+            } else {
+                return mLastUpdatedTimestampByPackage.getOrDefault(record.getSbn().getPackageName(),
+                        0L);
+            }
+        }
+
+        @PolitenessState int getNextState(@PolitenessState final int currState,
+                final long timeSinceLastNotif) {
+            @PolitenessState int nextState = currState;
+            switch (currState) {
+                case POLITE_STATE_DEFAULT:
+                    if (timeSinceLastNotif < mTimeoutPolite) {
+                        nextState = POLITE_STATE_POLITE;
+                    }
+                    break;
+                case POLITE_STATE_POLITE:
+                    if (timeSinceLastNotif < mTimeoutMuted) {
+                        nextState = POLITE_STATE_MUTED;
+                    } else if (timeSinceLastNotif > mTimeoutPolite) {
+                        nextState = POLITE_STATE_DEFAULT;
+                    } else {
+                        nextState = POLITE_STATE_POLITE;
+                    }
+                    break;
+                case POLITE_STATE_MUTED:
+                    if (timeSinceLastNotif > mTimeoutMuted) {
+                        nextState = POLITE_STATE_POLITE;
+                    } else {
+                        nextState = POLITE_STATE_MUTED;
+                    }
+                    break;
+                default:
+                    Log.w(TAG, "getNextState unexpected volume state: " + currState);
+                    break;
+            }
+            return nextState;
+        }
     }
 
     // TODO b/270456865: Only one of the two strategies will be released.
@@ -1143,72 +1222,51 @@
      *  after timeoutMuted.
      *  - Transitions back to the default state after a user interaction with a notification.
      */
-    public static class Strategy1 extends PolitenessStrategy {
+    private static class StrategyPerApp extends PolitenessStrategy {
         // Keep track of the number of notifications posted per channel
         private final Map<String, Integer> mNumPosted;
         // Reset to default state if number of posted notifications exceed this value when muted
         private final int mMaxPostedForReset;
 
-        public Strategy1(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted,
-                int maxPosted) {
+        public StrategyPerApp(int timeoutPolite, int timeoutMuted, int volumePolite,
+                int volumeMuted, int maxPosted) {
             super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
 
             mNumPosted = new HashMap<>();
             mMaxPostedForReset = maxPosted;
 
             if (DEBUG) {
-                Log.i(TAG, "Strategy1: " + timeoutPolite + " " + timeoutMuted);
+                Log.i(TAG, "StrategyPerApp: " + timeoutPolite + " " + timeoutMuted);
             }
         }
 
         @Override
         public void onNotificationPosted(final NotificationRecord record) {
-            long timeSinceLastNotif = System.currentTimeMillis()
-                    - record.getChannel().getLastNotificationUpdateTimeMs();
+            if (shouldIgnoreNotification(record)) {
+                return;
+            }
+
+            long timeSinceLastNotif =
+                    System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
 
             final String key = getChannelKey(record);
-            @PolitenessState int volState = getPolitenessState(record);
+            @PolitenessState final int currState = getPolitenessState(record);
+            @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
 
+            // Reset to default state if number of posted notifications exceed this value when muted
             int numPosted = mNumPosted.getOrDefault(key, 0) + 1;
             mNumPosted.put(key, numPosted);
-
-            switch (volState) {
-                case POLITE_STATE_DEFAULT:
-                    if (timeSinceLastNotif < mTimeoutPolite) {
-                        volState = POLITE_STATE_POLITE;
-                    }
-                    break;
-                case POLITE_STATE_POLITE:
-                    if (timeSinceLastNotif < mTimeoutMuted) {
-                        volState = POLITE_STATE_MUTED;
-                    } else if (timeSinceLastNotif > mTimeoutPolite) {
-                        volState = POLITE_STATE_DEFAULT;
-                    } else {
-                        volState = POLITE_STATE_POLITE;
-                    }
-                    break;
-                case POLITE_STATE_MUTED:
-                    if (timeSinceLastNotif > mTimeoutMuted) {
-                        volState = POLITE_STATE_POLITE;
-                    } else {
-                        volState = POLITE_STATE_MUTED;
-                    }
-                    if (numPosted >= mMaxPostedForReset) {
-                        volState = POLITE_STATE_DEFAULT;
-                        mNumPosted.put(key, 0);
-                    }
-                    break;
-                default:
-                    Log.w(TAG, "onNotificationPosted unexpected volume state: " + volState);
-                    break;
+            if (currState == POLITE_STATE_MUTED && numPosted >= mMaxPostedForReset) {
+                nextState = POLITE_STATE_DEFAULT;
+                mNumPosted.put(key, 0);
             }
 
             if (DEBUG) {
                 Log.i(TAG, "onNotificationPosted time delta: " + timeSinceLastNotif + " vol state: "
-                        + volState + " key: " + key + " numposted " + numPosted);
+                        + nextState + " key: " + key + " numposted " + numPosted);
             }
 
-            mVolumeStates.put(key, volState);
+            mVolumeStates.put(key, nextState);
         }
 
         @Override
@@ -1219,61 +1277,98 @@
     }
 
     /**
-     *  Polite notification strategy 2:
-     *   - Transitions from default (loud) => muted state if a notification
-     *   alerts the same channel before timeoutPolite.
-     *   - Transitions from polite => default state if a notification
-     *  alerts the same channel before timeoutMuted.
-     *   - Transitions from muted => default state if a notification alerts after timeoutMuted,
-     *  otherwise transitions to the polite state.
-     *   - Transitions back to the default state after a user interaction with a notification.
+     * Global (cross-app) strategy.
      */
-    public static class Strategy2 extends PolitenessStrategy {
-        public Strategy2(int timeoutPolite, int timeoutMuted, int volumePolite, int volumeMuted) {
+    private static class StrategyGlobal extends PolitenessStrategy {
+        private static final String COMMON_KEY = "cross_app_common_key";
+
+        private final PolitenessStrategy mAppStrategy;
+        private long mLastNotificationTimestamp = 0;
+
+        public StrategyGlobal(int timeoutPolite, int timeoutMuted, int volumePolite,
+                int volumeMuted, PolitenessStrategy appStrategy) {
             super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted);
 
+            mAppStrategy = appStrategy;
+
             if (DEBUG) {
-                Log.i(TAG, "Strategy2: " + timeoutPolite + " " + timeoutMuted);
+                Log.i(TAG, "StrategyGlobal: " + timeoutPolite + " " + timeoutMuted);
             }
         }
 
         @Override
-        public void onNotificationPosted(final NotificationRecord record) {
-            long timeSinceLastNotif = System.currentTimeMillis()
-                    - record.getChannel().getLastNotificationUpdateTimeMs();
+        void onNotificationPosted(NotificationRecord record) {
+            if (shouldIgnoreNotification(record)) {
+                return;
+            }
+
+            long timeSinceLastNotif =
+                    System.currentTimeMillis() - getLastNotificationUpdateTimeMs(record);
 
             final String key = getChannelKey(record);
-            @PolitenessState int volState = getPolitenessState(record);
-
-            switch (volState) {
-                case POLITE_STATE_DEFAULT:
-                    if (timeSinceLastNotif < mTimeoutPolite) {
-                        volState = POLITE_STATE_MUTED;
-                    }
-                    break;
-                case POLITE_STATE_POLITE:
-                    if (timeSinceLastNotif > mTimeoutMuted) {
-                        volState = POLITE_STATE_DEFAULT;
-                    }
-                    break;
-                case POLITE_STATE_MUTED:
-                    if (timeSinceLastNotif > mTimeoutMuted) {
-                        volState = POLITE_STATE_DEFAULT;
-                    } else {
-                        volState = POLITE_STATE_POLITE;
-                    }
-                    break;
-                default:
-                    Log.w(TAG, "onNotificationPosted unexpected volume state: " + volState);
-                    break;
-            }
+            @PolitenessState final int currState = getPolitenessState(record);
+            @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif);
 
             if (DEBUG) {
-                Log.i(TAG, "onNotificationPosted time delta: " + timeSinceLastNotif + " vol state: "
-                        + volState + " key: " + key);
+                Log.i(TAG, "StrategyGlobal onNotificationPosted time delta: " + timeSinceLastNotif
+                        + " vol state: " + nextState + " key: " + key);
             }
 
-            mVolumeStates.put(key, volState);
+            mVolumeStates.put(key, nextState);
+
+            mAppStrategy.onNotificationPosted(record);
+        }
+
+        @Override
+        public float getSoundVolume(final NotificationRecord record) {
+            final @PolitenessState int globalVolState = getPolitenessState(record);
+            final @PolitenessState int appVolState = mAppStrategy.getPolitenessState(record);
+
+            // Prioritize the most polite outcome
+            if (globalVolState > appVolState) {
+                return super.getSoundVolume(record);
+            } else {
+                return mAppStrategy.getSoundVolume(record);
+            }
+        }
+
+        @Override
+        public void onUserInteraction(final NotificationRecord record) {
+            super.onUserInteraction(record);
+            mAppStrategy.onUserInteraction(record);
+        }
+
+        @Override
+        String getChannelKey(final NotificationRecord record) {
+            // If the user explicitly changed the channel notification sound:
+            // handle as a separate channel
+            if (record.getChannel().hasUserSetSound()) {
+                return super.getChannelKey(record);
+            } else {
+                // Use one global key per user
+                return record.getSbn().getNormalizedUserId() + ":" + COMMON_KEY;
+            }
+        }
+
+        @Override
+        public void setLastNotificationUpdateTimeMs(NotificationRecord record,
+                long timestampMillis) {
+            super.setLastNotificationUpdateTimeMs(record, timestampMillis);
+            mLastNotificationTimestamp = timestampMillis;
+        }
+
+        long getLastNotificationUpdateTimeMs(final NotificationRecord record) {
+            if (record.getChannel().hasUserSetSound()) {
+                return super.getLastNotificationUpdateTimeMs(record);
+            } else {
+                return mLastNotificationTimestamp;
+            }
+        }
+
+        @Override
+        void setApplyCooldownPerPackage(boolean applyPerPackage) {
+            super.setApplyCooldownPerPackage(applyPerPackage);
+            mAppStrategy.setApplyCooldownPerPackage(applyPerPackage);
         }
     }
 
@@ -1338,7 +1433,7 @@
                     updateLightsLocked();
                 }
             }
-            if (mEnablePoliteNotificationsFeature) {
+            if (Flags.politeNotifications()) {
                 if (NOTIFICATION_COOLDOWN_ENABLED_URI.equals(uri)) {
                     mNotificationCooldownEnabled = Settings.System.getIntForUser(
                             mContext.getContentResolver(),
@@ -1363,13 +1458,16 @@
                             Settings.System.NOTIFICATION_COOLDOWN_ALL,
                             DEFAULT_NOTIFICATION_COOLDOWN_ALL, UserHandle.USER_CURRENT)
                             != 0;
+                    mStrategy.setApplyCooldownPerPackage(mNotificationCooldownApplyToAll);
                 }
-                if (NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI.equals(uri)) {
-                    mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
+                if (Flags.vibrateWhileUnlocked()) {
+                    if (NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED_URI.equals(uri)) {
+                        mNotificationCooldownVibrateUnlocked = Settings.System.getIntForUser(
                             mContext.getContentResolver(),
                             Settings.System.NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
                             DEFAULT_NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED,
                             UserHandle.USER_CURRENT) != 0;
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryManager.java b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
index 6a46048..e3880c3 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryManager.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
@@ -118,6 +118,10 @@
         }
     }
 
+    public void onUserAdded(@UserIdInt int userId) {
+        mSettingsObserver.update(null, userId);
+    }
+
     public void onUserStopped(@UserIdInt int userId) {
         synchronized (mLock) {
             mUserUnlockedStates.put(userId, false);
@@ -401,9 +405,7 @@
                     false, this, UserHandle.USER_ALL);
             synchronized (mLock) {
                 for (UserInfo userInfo : mUserManager.getUsers()) {
-                    if (!userInfo.isProfile()) {
-                        update(null, userInfo.id);
-                    }
+                    update(null, userInfo.id);
                 }
             }
         }
@@ -424,10 +426,7 @@
                 boolean historyEnabled = Settings.Secure.getIntForUser(resolver,
                         Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, userId)
                         != 0;
-                int[] profiles = mUserManager.getProfileIds(userId, true);
-                for (int profileId : profiles) {
-                    onHistoryEnabledChanged(profileId, historyEnabled);
-                }
+                onHistoryEnabledChanged(userId, historyEnabled);
             }
         }
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7fbc085..9ed3559 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2025,6 +2025,8 @@
                     if (!mUserProfiles.isProfileUser(userId)) {
                         allowDefaultApprovedServices(userId);
                     }
+                    mHistoryManager.onUserAdded(userId);
+                    mSettingsObserver.update(null, userId);
                 }
             } else if (action.equals(Intent.ACTION_USER_REMOVED)) {
                 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
@@ -2137,13 +2139,8 @@
                 mPreferencesHelper.updateBubblesEnabled();
             }
             if (uri == null || NOTIFICATION_HISTORY_ENABLED.equals(uri)) {
-                final IntArray userIds = mUserProfiles.getCurrentProfileIds();
-
-                for (int i = 0; i < userIds.size(); i++) {
-                    mArchive.updateHistoryEnabled(userIds.get(i),
-                            Settings.Secure.getIntForUser(resolver,
-                                    Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0,
-                                    userIds.get(i)) == 1);
+                for (UserInfo userInfo : mUm.getUsers()) {
+                    update(uri, userInfo.id);
                 }
             }
             if (uri == null || NOTIFICATION_SHOW_MEDIA_ON_QUICK_SETTINGS_URI.equals(uri)) {
@@ -2164,6 +2161,17 @@
                 }
             }
         }
+
+        public void update(Uri uri, int userId) {
+            ContentResolver resolver = getContext().getContentResolver();
+            if (uri == null || NOTIFICATION_HISTORY_ENABLED.equals(uri)) {
+                mArchive.updateHistoryEnabled(userId,
+                        Settings.Secure.getIntForUser(resolver,
+                                Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0,
+                                userId) == 1);
+                // note: this setting is also handled in NotificationHistoryManager
+            }
+        }
     }
 
     private SettingsObserver mSettingsObserver;
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 49db7fc..47967db 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -56,4 +56,11 @@
   namespace: "systemui"
   description: "When this flag is on, NMS will no longer call removeMessage() and hasCallbacks() on Handler"
   bug: "311051285"
+}
+
+flag {
+  name: "notification_test"
+  namespace: "systemui"
+  description: "Timing test, no functionality"
+  bug: "316931130"
 }
\ No newline at end of file
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index f6e7ef3..9ce3cb3 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -41,14 +41,13 @@
 import android.system.StructStat;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.proto.ProtoInputStream;
-import android.util.proto.ProtoParseException;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.BootReceiver;
 import com.android.server.ServiceThread;
 import com.android.server.os.TombstoneProtos.Cause;
 import com.android.server.os.TombstoneProtos.Tombstone;
+import com.android.server.os.protobuf.CodedInputStream;
 
 import libcore.io.IoUtils;
 
@@ -130,18 +129,21 @@
             return;
         }
 
-        String processName = "UNKNOWN";
         final boolean isProtoFile = filename.endsWith(".pb");
-        File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb");
-
-        Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile);
-        if (parsedTombstone.isPresent()) {
-            processName = parsedTombstone.get().getProcessName();
+        if (!isProtoFile) {
+            return;
         }
-        BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName, mTmpFileLock);
+
+        Optional<ParsedTombstone> parsedTombstone = handleProtoTombstone(path, true);
+        if (parsedTombstone.isPresent()) {
+            BootReceiver.addTombstoneToDropBox(
+                    mContext, path, parsedTombstone.get().getTombstone(),
+                    parsedTombstone.get().getProcessName(), mTmpFileLock);
+        }
     }
 
-    private Optional<TombstoneFile> handleProtoTombstone(File path, boolean addToList) {
+    private Optional<ParsedTombstone> handleProtoTombstone(
+            File path, boolean addToList) {
         final String filename = path.getName();
         if (!filename.endsWith(".pb")) {
             Slog.w(TAG, "unexpected tombstone name: " + path);
@@ -171,7 +173,7 @@
             return Optional.empty();
         }
 
-        final Optional<TombstoneFile> parsedTombstone = TombstoneFile.parse(pfd);
+        final Optional<ParsedTombstone> parsedTombstone = TombstoneFile.parse(pfd);
         if (!parsedTombstone.isPresent()) {
             IoUtils.closeQuietly(pfd);
             return Optional.empty();
@@ -184,7 +186,7 @@
                     previous.dispose();
                 }
 
-                mTombstones.put(number, parsedTombstone.get());
+                mTombstones.put(number, parsedTombstone.get().getTombstoneFile());
             }
         }
 
@@ -332,6 +334,27 @@
         }
     }
 
+    static class ParsedTombstone {
+        TombstoneFile mTombstoneFile;
+        Tombstone mTombstone;
+        ParsedTombstone(TombstoneFile tombstoneFile, Tombstone tombstone) {
+            mTombstoneFile = tombstoneFile;
+            mTombstone = tombstone;
+        }
+
+        public String getProcessName() {
+            return mTombstoneFile.getProcessName();
+        }
+
+        public TombstoneFile getTombstoneFile() {
+            return mTombstoneFile;
+        }
+
+        public Tombstone getTombstone() {
+            return mTombstone;
+        }
+    }
+
     static class TombstoneFile {
         final ParcelFileDescriptor mPfd;
 
@@ -414,67 +437,21 @@
             }
         }
 
-        static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) {
-            final FileInputStream is = new FileInputStream(pfd.getFileDescriptor());
-            final ProtoInputStream stream = new ProtoInputStream(is);
+        static Optional<ParsedTombstone> parse(ParcelFileDescriptor pfd) {
+            Tombstone tombstoneProto;
+            try (FileInputStream is = new FileInputStream(pfd.getFileDescriptor())) {
+                final byte[] tombstoneBytes = is.readAllBytes();
 
-            int pid = 0;
-            int uid = 0;
-            String processName = null;
-            String crashReason = "";
-            String selinuxLabel = "";
-
-            try {
-                while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-                    switch (stream.getFieldNumber()) {
-                        case (int) Tombstone.PID:
-                            pid = stream.readInt(Tombstone.PID);
-                            break;
-
-                        case (int) Tombstone.UID:
-                            uid = stream.readInt(Tombstone.UID);
-                            break;
-
-                        case (int) Tombstone.COMMAND_LINE:
-                            if (processName == null) {
-                                processName = stream.readString(Tombstone.COMMAND_LINE);
-                            }
-                            break;
-
-                        case (int) Tombstone.CAUSES:
-                            if (!crashReason.equals("")) {
-                                // Causes appear in decreasing order of likelihood. For now we only
-                                // want the most likely crash reason here, so ignore all others.
-                                break;
-                            }
-                            long token = stream.start(Tombstone.CAUSES);
-                        cause:
-                            while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
-                                switch (stream.getFieldNumber()) {
-                                    case (int) Cause.HUMAN_READABLE:
-                                        crashReason = stream.readString(Cause.HUMAN_READABLE);
-                                        break cause;
-
-                                    default:
-                                        break;
-                                }
-                            }
-                            stream.end(token);
-                            break;
-
-                        case (int) Tombstone.SELINUX_LABEL:
-                            selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL);
-                            break;
-
-                        default:
-                            break;
-                    }
-                }
-            } catch (IOException | ProtoParseException ex) {
+                tombstoneProto = Tombstone.parseFrom(
+                        CodedInputStream.newInstance(tombstoneBytes));
+            } catch (IOException ex) {
                 Slog.e(TAG, "Failed to parse tombstone", ex);
                 return Optional.empty();
             }
 
+            int pid = tombstoneProto.getPid();
+            int uid = tombstoneProto.getUid();
+
             if (!UserHandle.isApp(uid)) {
                 Slog.e(TAG, "Tombstone's UID (" + uid + ") not an app, ignoring");
                 return Optional.empty();
@@ -491,6 +468,7 @@
             final int userId = UserHandle.getUserId(uid);
             final int appId = UserHandle.getAppId(uid);
 
+            String selinuxLabel = tombstoneProto.getSelinuxLabel();
             if (!selinuxLabel.startsWith("u:r:untrusted_app")) {
                 Slog.e(TAG, "Tombstone has invalid selinux label (" + selinuxLabel + "), ignoring");
                 return Optional.empty();
@@ -502,11 +480,30 @@
             result.mAppId = appId;
             result.mPid = pid;
             result.mUid = uid;
-            result.mProcessName = processName == null ? "" : processName;
+            result.mProcessName = getCmdLineProcessName(tombstoneProto);
             result.mTimestampMs = timestampMs;
-            result.mCrashReason = crashReason;
+            result.mCrashReason = getCrashReason(tombstoneProto);
 
-            return Optional.of(result);
+            return Optional.of(new ParsedTombstone(result, tombstoneProto));
+        }
+
+        private static String getCmdLineProcessName(Tombstone tombstoneProto) {
+            for (String cmdline : tombstoneProto.getCommandLineList()) {
+                if (cmdline != null) {
+                    return cmdline;
+                }
+            }
+            return "";
+        }
+
+        private static String getCrashReason(Tombstone tombstoneProto) {
+            for (Cause cause : tombstoneProto.getCausesList()) {
+                if (cause.getHumanReadable() != null
+                        && !cause.getHumanReadable().equals("")) {
+                    return cause.getHumanReadable();
+                }
+            }
+            return "";
         }
 
         public IParcelFileDescriptorRetriever getPfdRetriever() {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 992d8eb..dd9541e 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -175,6 +175,7 @@
 import com.android.server.art.model.ArtFlags;
 import com.android.server.art.model.DexoptParams;
 import com.android.server.art.model.DexoptResult;
+import com.android.server.criticalevents.CriticalEventLog;
 import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.dex.DexManager;
@@ -957,6 +958,7 @@
         final Set<String> scannedPackages = new ArraySet<>(requests.size());
         final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
         final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
+        CriticalEventLog.getInstance().logInstallPackagesStarted();
         boolean success = false;
         try {
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackagesLI");
diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
index d05e4c6..cf5de89 100644
--- a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
+++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java
@@ -48,6 +48,7 @@
 class PackageMonitorCallbackHelper {
 
     private static final boolean DEBUG = false;
+    private static final String TAG = "PackageMonitorCallbackHelper";
 
     @NonNull
     private final Object mLock = new Object();
@@ -243,25 +244,33 @@
                 return;
             }
             int registerUid = registerUser.getUid();
+            if (allowUids != null && registerUid != Process.SYSTEM_UID
+                    && !ArrayUtils.contains(allowUids, registerUid)) {
+                if (DEBUG) {
+                    Slog.w(TAG, "Skip invoke PackageMonitorCallback for " + intent.getAction()
+                            + ", uid " + registerUid);
+                }
+                return;
+            }
+            Intent newIntent = intent;
             if (filterExtrasFunction != null) {
                 final Bundle extras = intent.getExtras();
                 if (extras != null) {
                     final Bundle filteredExtras = filterExtrasFunction.apply(registerUid, extras);
-                    if (filteredExtras != null) {
-                        intent.replaceExtras(filteredExtras);
+                    if (filteredExtras == null) {
+                        // caller is unable to access this intent
+                        if (DEBUG) {
+                            Slog.w(TAG,
+                                    "Skip invoke PackageMonitorCallback for " + intent.getAction()
+                                            + " because null filteredExtras");
+                        }
+                        return;
                     }
+                    newIntent = new Intent(newIntent);
+                    newIntent.replaceExtras(filteredExtras);
                 }
             }
-            if (allowUids != null && registerUid != Process.SYSTEM_UID
-                    && !ArrayUtils.contains(allowUids, registerUid)) {
-                if (DEBUG) {
-                    Slog.w("PackageMonitorCallbackHelper",
-                            "Skip invoke PackageMonitorCallback for " + intent.getAction()
-                                    + ", uid " + registerUid);
-                }
-                return;
-            }
-            invokeCallback(callback, intent);
+            invokeCallback(callback, newIntent);
         }));
     }
 
diff --git a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
index 752eb53..17c901e 100644
--- a/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
+++ b/services/core/java/com/android/server/pm/verify/domain/proxy/DomainVerificationProxyV1.java
@@ -254,6 +254,14 @@
             String packageName = verifications.valueAt(index).second;
             AndroidPackage pkg = mConnection.getPackage(packageName);
 
+            if (pkg == null) {
+                if (DEBUG_BROADCASTS) {
+                    Slog.d(TAG,
+                            "Skip sendBroadcasts because null AndroidPackage for " + packageName);
+                }
+                continue;
+            }
+
             String hostsString = buildHostsString(pkg);
 
             Intent intent = new Intent(Intent.ACTION_INTENT_FILTER_NEEDS_VERIFICATION)
diff --git a/services/core/java/com/android/server/policy/Android.bp b/services/core/java/com/android/server/policy/Android.bp
new file mode 100644
index 0000000..fa55bf0
--- /dev/null
+++ b/services/core/java/com/android/server/policy/Android.bp
@@ -0,0 +1,10 @@
+aconfig_declarations {
+    name: "policy_flags",
+    package: "com.android.server.policy",
+    srcs: ["*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "policy_flags_lib",
+    aconfig_declarations: "policy_flags",
+}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f651dbf..bf669fb 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1301,7 +1301,7 @@
                     Settings.Global.putInt(mContext.getContentResolver(),
                             Settings.Global.THEATER_MODE_ON, 0);
                     if (!interactive) {
-                        wakeUpFromWakeKey(eventTime, KEYCODE_POWER);
+                        wakeUpFromWakeKey(eventTime, KEYCODE_POWER, /* isDown= */ false);
                     }
                 } else {
                     Slog.i(TAG, "Toggling theater mode on.");
@@ -1317,7 +1317,7 @@
             case MULTI_PRESS_POWER_BRIGHTNESS_BOOST:
                 Slog.i(TAG, "Starting brightness boost.");
                 if (!interactive) {
-                    wakeUpFromWakeKey(eventTime, KEYCODE_POWER);
+                    wakeUpFromWakeKey(eventTime, KEYCODE_POWER, /* isDown= */ false);
                 }
                 mPowerManager.boostScreenBrightness(eventTime);
                 break;
@@ -5185,7 +5185,8 @@
     public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
             long whenNanos, int policyFlags) {
         if ((policyFlags & FLAG_WAKE) != 0) {
-            if (mWindowWakeUpPolicy.wakeUpFromMotion(whenNanos / 1000000)) {
+            if (mWindowWakeUpPolicy.wakeUpFromMotion(
+                        whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
                 // Woke up. Pass motion events to user.
                 return ACTION_PASS_TO_USER;
             }
@@ -5199,7 +5200,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)) {
+            if (mWindowWakeUpPolicy.wakeUpFromMotion(
+                        whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
                 // Woke up. Pass motion events to user.
                 return ACTION_PASS_TO_USER;
             }
@@ -5534,11 +5536,14 @@
     }
 
     private void wakeUpFromWakeKey(KeyEvent event) {
-        wakeUpFromWakeKey(event.getEventTime(), event.getKeyCode());
+        wakeUpFromWakeKey(
+                event.getEventTime(),
+                event.getKeyCode(),
+                event.getAction() == KeyEvent.ACTION_DOWN);
     }
 
-    private void wakeUpFromWakeKey(long eventTime, int keyCode) {
-        if (mWindowWakeUpPolicy.wakeUpFromKey(eventTime, keyCode)) {
+    private void wakeUpFromWakeKey(long eventTime, int keyCode, boolean isDown) {
+        if (mWindowWakeUpPolicy.wakeUpFromKey(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 392d0d4..a790950 100644
--- a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
@@ -24,6 +24,9 @@
 import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
 import static android.view.KeyEvent.KEYCODE_POWER;
 
+import static com.android.server.policy.Flags.supportInputWakeupDelegate;
+
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.PowerManager;
@@ -31,7 +34,11 @@
 import android.os.SystemClock;
 import android.provider.Settings;
 import android.util.Slog;
+import android.view.KeyEvent;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.Clock;
+import com.android.server.LocalServices;
 
 /** Policy controlling the decision and execution of window-related wake ups. */
 class WindowWakeUpPolicy {
@@ -41,18 +48,27 @@
 
     private final Context mContext;
     private final PowerManager mPowerManager;
+    private final Clock mClock;
 
     private final boolean mAllowTheaterModeWakeFromKey;
     private final boolean mAllowTheaterModeWakeFromPowerKey;
     private final boolean mAllowTheaterModeWakeFromMotion;
-    private final boolean mAllowTheaterModeWakeFromMotionWhenNotDreaming;
     private final boolean mAllowTheaterModeWakeFromCameraLens;
     private final boolean mAllowTheaterModeWakeFromLidSwitch;
     private final boolean mAllowTheaterModeWakeFromWakeGesture;
 
+    // The policy will handle input-based wake ups if this delegate is null.
+    @Nullable private WindowWakeUpPolicyInternal.InputWakeUpDelegate mInputWakeUpDelegate;
+
     WindowWakeUpPolicy(Context context) {
+        this(context, Clock.SYSTEM_CLOCK);
+    }
+
+    @VisibleForTesting
+    WindowWakeUpPolicy(Context context, Clock clock) {
         mContext = context;
         mPowerManager = context.getSystemService(PowerManager.class);
+        mClock = clock;
 
         final Resources res = context.getResources();
         mAllowTheaterModeWakeFromKey = res.getBoolean(
@@ -62,14 +78,26 @@
                     com.android.internal.R.bool.config_allowTheaterModeWakeFromPowerKey);
         mAllowTheaterModeWakeFromMotion = res.getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromMotion);
-        mAllowTheaterModeWakeFromMotionWhenNotDreaming = res.getBoolean(
-                com.android.internal.R.bool.config_allowTheaterModeWakeFromMotionWhenNotDreaming);
         mAllowTheaterModeWakeFromCameraLens = res.getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromCameraLens);
         mAllowTheaterModeWakeFromLidSwitch = res.getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch);
         mAllowTheaterModeWakeFromWakeGesture = res.getBoolean(
                 com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture);
+        if (supportInputWakeupDelegate()) {
+            LocalServices.addService(WindowWakeUpPolicyInternal.class, new LocalService());
+        }
+    }
+
+    private final class LocalService implements WindowWakeUpPolicyInternal {
+        @Override
+        public void setInputWakeUpDelegate(@Nullable InputWakeUpDelegate delegate) {
+            if (!supportInputWakeupDelegate()) {
+                Slog.w(TAG, "Input wake up delegates not supported.");
+                return;
+            }
+            mInputWakeUpDelegate = delegate;
+        }
     }
 
     /**
@@ -77,31 +105,49 @@
      *
      * @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 wakeUpFromKey(long eventTime, int keyCode, boolean isDown) {
         final boolean wakeAllowedDuringTheaterMode =
                 keyCode == KEYCODE_POWER
                         ? mAllowTheaterModeWakeFromPowerKey
                         : mAllowTheaterModeWakeFromKey;
-        return wakeUp(
+        if (!canWakeUp(wakeAllowedDuringTheaterMode)) {
+            if (DEBUG) Slog.d(TAG, "Unable to wake up from " + KeyEvent.keyCodeToString(keyCode));
+            return false;
+        }
+        if (mInputWakeUpDelegate != null
+                && mInputWakeUpDelegate.wakeUpFromKey(eventTime, keyCode, isDown)) {
+            return true;
+        }
+        wakeUp(
                 eventTime,
-                wakeAllowedDuringTheaterMode,
                 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 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) {
-        return wakeUp(
-                eventTime, mAllowTheaterModeWakeFromMotion, WAKE_REASON_WAKE_MOTION, "MOTION");
+    boolean wakeUpFromMotion(long eventTime, int source, boolean isDown) {
+        if (!canWakeUp(mAllowTheaterModeWakeFromMotion)) {
+            if (DEBUG) Slog.d(TAG, "Unable to wake up from motion.");
+            return false;
+        }
+        if (mInputWakeUpDelegate != null
+                && mInputWakeUpDelegate.wakeUpFromMotion(eventTime, source, isDown)) {
+            return true;
+        }
+        wakeUp(eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
+        return true;
     }
 
     /**
@@ -112,11 +158,12 @@
      *      executed; {@code false} otherwise.
      */
     boolean wakeUpFromCameraCover(long eventTime) {
-        return wakeUp(
-                eventTime,
-                mAllowTheaterModeWakeFromCameraLens,
-                WAKE_REASON_CAMERA_LAUNCH,
-                "CAMERA_COVER");
+        if (!canWakeUp(mAllowTheaterModeWakeFromCameraLens)) {
+            if (DEBUG) Slog.d(TAG, "Unable to wake up from camera cover.");
+            return false;
+        }
+        wakeUp(eventTime, WAKE_REASON_CAMERA_LAUNCH, "CAMERA_COVER");
+        return true;
     }
 
     /**
@@ -126,11 +173,12 @@
      *      executed; {@code false} otherwise.
      */
     boolean wakeUpFromLid() {
-        return wakeUp(
-                SystemClock.uptimeMillis(),
-                mAllowTheaterModeWakeFromLidSwitch,
-                WAKE_REASON_LID,
-                "LID");
+        if (!canWakeUp(mAllowTheaterModeWakeFromLidSwitch)) {
+            if (DEBUG) Slog.d(TAG, "Unable to wake up from lid.");
+            return false;
+        }
+        wakeUp(mClock.uptimeMillis(), WAKE_REASON_LID, "LID");
+        return true;
     }
 
     /**
@@ -140,11 +188,12 @@
      *      executed; {@code false} otherwise.
      */
     boolean wakeUpFromPowerKeyCameraGesture() {
-        return wakeUp(
-                SystemClock.uptimeMillis(),
-                mAllowTheaterModeWakeFromPowerKey,
-                WAKE_REASON_CAMERA_LAUNCH,
-                "CAMERA_GESTURE_PREVENT_LOCK");
+        if (!canWakeUp(mAllowTheaterModeWakeFromPowerKey)) {
+            if (DEBUG) Slog.d(TAG, "Unable to wake up from power key camera gesture.");
+            return false;
+        }
+        wakeUp(mClock.uptimeMillis(), WAKE_REASON_CAMERA_LAUNCH, "CAMERA_GESTURE_PREVENT_LOCK");
+        return true;
     }
 
     /**
@@ -154,23 +203,23 @@
      *      executed; {@code false} otherwise.
      */
     boolean wakeUpFromWakeGesture() {
-        return wakeUp(
-                SystemClock.uptimeMillis(),
-                mAllowTheaterModeWakeFromWakeGesture,
-                WAKE_REASON_GESTURE,
-                "GESTURE");
+        if (!canWakeUp(mAllowTheaterModeWakeFromWakeGesture)) {
+            if (DEBUG) Slog.d(TAG, "Unable to wake up from gesture.");
+            return false;
+        }
+        wakeUp(mClock.uptimeMillis(), WAKE_REASON_GESTURE, "GESTURE");
+        return true;
     }
 
-    private boolean wakeUp(
-            long wakeTime, boolean wakeInTheaterMode, @WakeReason int reason, String details) {
+    private boolean canWakeUp(boolean wakeInTheaterMode) {
         final boolean isTheaterModeEnabled =
                 Settings.Global.getInt(
                         mContext.getContentResolver(), Settings.Global.THEATER_MODE_ON, 0) == 1;
-        if (!wakeInTheaterMode && isTheaterModeEnabled) {
-            if (DEBUG) Slog.d(TAG, "Unable to wake up from " + details);
-            return false;
-        }
+        return wakeInTheaterMode || !isTheaterModeEnabled;
+    }
+
+    /** Wakes up {@link PowerManager}. */
+    private void wakeUp(long wakeTime, @WakeReason int reason, String details) {
         mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details);
-        return true;
     }
 }
diff --git a/services/core/java/com/android/server/policy/WindowWakeUpPolicyInternal.java b/services/core/java/com/android/server/policy/WindowWakeUpPolicyInternal.java
new file mode 100644
index 0000000..66a0035
--- /dev/null
+++ b/services/core/java/com/android/server/policy/WindowWakeUpPolicyInternal.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import android.annotation.Nullable;
+import android.os.SystemClock;
+
+import com.android.internal.annotations.Keep;
+import com.android.server.LocalServices;
+
+/** Policy controlling the decision and execution of window-related wake ups. */
+@Keep
+public interface WindowWakeUpPolicyInternal {
+
+    /**
+     * A delegate that can choose to intercept Input-related wake ups.
+     *
+     * <p>This delegate is not meant to control policy decisions on whether or not to wake up. The
+     * policy makes that decision, and forwards the wake up request to the delegate as necessary.
+     * Therefore, the role of the delegate is to handle the actual "waking" of the device in
+     * response to the respective input event.
+     */
+    @Keep
+    interface InputWakeUpDelegate {
+        /**
+         * Wakes up the device in response to a key event.
+         *
+         * @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 delegate handled the wake up. {@code false} if the delegate
+         *      decided not to handle the wake up. The policy will execute the wake up in this case.
+         */
+        boolean wakeUpFromKey(long eventTime, int keyCode, boolean isDown);
+        /**
+         * Wakes up the device in response to a motion event.
+         *
+         * @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
+         * @param source the {@link android.view.InputDevice} source that caused the event.
+         * @param isDown {@code true} if the event's action is {@link MotionEvent#ACTION_DOWN}.
+         * @return {@code true} if the delegate handled the wake up. {@code false} if the delegate
+         *      decided not to handle the wake up. The policy will execute the wake up in this case.
+         */
+        boolean wakeUpFromMotion(long eventTime, int source, boolean isDown);
+    }
+
+    /**
+     * Allows injecting a delegate for controlling input-based wake ups.
+     *
+     * <p>A delegate can be injected to the policy by system_server components only, and should be
+     * done via the {@link LocalServices} interface.
+     *
+     * <p>There can at most be one active delegate. If there's no delegate set (or if a {@code null}
+     * delegate is set), the policy will handle waking up the device in response to input events.
+     *
+     * @param delegate an implementation of {@link InputWakeUpDelegate} that handles input-based
+     *      wake up requests. {@code null} to let the policy handle these wake ups.
+     */
+    @Keep
+    void setInputWakeUpDelegate(@Nullable InputWakeUpDelegate delegate);
+}
diff --git a/services/core/java/com/android/server/policy/window_policy_flags.aconfig b/services/core/java/com/android/server/policy/window_policy_flags.aconfig
new file mode 100644
index 0000000..ed981e0
--- /dev/null
+++ b/services/core/java/com/android/server/policy/window_policy_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.policy"
+
+flag {
+    name: "support_input_wakeup_delegate"
+    namespace: "wear_frameworks"
+    description: "Whether or not window policy allows injecting input wake-up delegate."
+    bug: "298055811"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 5c603c2..5d716fc 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1440,6 +1440,13 @@
         if (biometricManager == null) {
             return new long[0];
         }
+        if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()
+                && mLockPatternUtils.isProfileWithUnifiedChallenge(userId)) {
+            // Profiles with unified challenge have their own set of biometrics, but the device
+            // unlock happens via the parent user.  In this case Keystore needs to be given the list
+            // of biometric SIDs from the parent user, not the profile.
+            userId = resolveProfileParent(userId);
+        }
         return biometricManager.getAuthenticatorIds(userId);
     }
 
diff --git a/services/core/java/com/android/server/wm/SensitiveContentPackages.java b/services/core/java/com/android/server/wm/SensitiveContentPackages.java
new file mode 100644
index 0000000..3862b82
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SensitiveContentPackages.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.NonNull;
+import android.util.ArraySet;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Cache of distinct package/uid pairs that require being blocked from screen capture. This class is
+ * not threadsafe and any call site should hold {@link WindowManagerGlobalLock}
+ */
+public class SensitiveContentPackages {
+    private final ArraySet<PackageInfo> mProtectedPackages = new ArraySet<>();
+
+    /** Returns {@code true} if package/uid pair should be blocked from screen capture */
+    public boolean shouldBlockScreenCaptureForApp(String pkg, int uid) {
+        for (int i = 0; i < mProtectedPackages.size(); i++) {
+            PackageInfo info = mProtectedPackages.valueAt(i);
+            if (info != null && info.mPkg.equals(pkg) && info.mUid == uid) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Replaces the set of package/uid pairs to set that should be blocked from screen capture */
+    public void setShouldBlockScreenCaptureForApp(@NonNull Set<PackageInfo> packageInfos) {
+        mProtectedPackages.clear();
+        mProtectedPackages.addAll(packageInfos);
+    }
+
+    void dump(PrintWriter pw) {
+        final String innerPrefix = "  ";
+        pw.println("SensitiveContentPackages:");
+        pw.println(innerPrefix + "Packages that should block screen capture ("
+                + mProtectedPackages.size() + "):");
+        for (PackageInfo info : mProtectedPackages) {
+            pw.println(innerPrefix + "  package=" + info.mPkg + "  uid=" + info.mUid);
+        }
+    }
+
+    /** Helper class that represents a package/uid pair */
+    public static class PackageInfo {
+        private String mPkg;
+        private int mUid;
+
+        public PackageInfo(String pkg, int uid) {
+            this.mPkg = pkg;
+            this.mUid = uid;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof PackageInfo)) return false;
+            PackageInfo that = (PackageInfo) o;
+            return mUid == that.mUid && Objects.equals(mPkg, that.mPkg);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mPkg, mUid);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 3b06343..a7a6bf2 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3699,6 +3699,16 @@
                 }
                 wc.assignLayer(t, layer++);
 
+                // Boost the adjacent TaskFragment for dimmer if needed.
+                final TaskFragment taskFragment = wc.asTaskFragment();
+                if (taskFragment != null && taskFragment.isEmbedded()
+                        && taskFragment.isVisibleRequested()) {
+                    final TaskFragment adjacentTf = taskFragment.getAdjacentTaskFragment();
+                    if (adjacentTf != null && adjacentTf.shouldBoostDimmer()) {
+                        adjacentTf.assignLayer(t, layer++);
+                    }
+                }
+
                 // Place the decor surface just above the owner TaskFragment.
                 if (mDecorSurfaceContainer != null && !decorSurfacePlaced
                         && wc == mDecorSurfaceContainer.mOwnerTaskFragment) {
@@ -4785,6 +4795,7 @@
         }
         if (top.isAttached()) {
             top.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+            top.mWaitForEnteringPinnedMode = false;
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index f51bd1b..f56759f 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -39,6 +39,7 @@
 import static android.os.Process.SYSTEM_UID;
 import static android.os.UserHandle.USER_NULL;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
 import static android.view.WindowManager.TRANSIT_NONE;
@@ -2995,6 +2996,30 @@
         }, false /* traverseTopToBottom */);
     }
 
+    boolean shouldBoostDimmer() {
+        if (asTask() != null || !isDimmingOnParentTask()) {
+            // early return if not embedded or should not dim on parent Task.
+            return false;
+        }
+
+        final TaskFragment adjacentTf = getAdjacentTaskFragment();
+        if (adjacentTf == null) {
+            // early return if no adjacent TF.
+            return false;
+        }
+
+        if (getParent().mChildren.indexOf(adjacentTf) < getParent().mChildren.indexOf(this)) {
+            // early return if this TF already has higher z-ordering.
+            return false;
+        }
+
+        // boost if there's an Activity window that has FLAG_DIM_BEHIND flag.
+        return forAllWindows(
+                (w) -> (w.mAttrs.flags & FLAG_DIM_BEHIND) != 0 && w.mActivityRecord != null
+                        && w.mActivityRecord.isEmbedded() && (w.mActivityRecord.isVisibleRequested()
+                        || w.mActivityRecord.isVisible()), true);
+    }
+
     @Override
     Dimmer getDimmer() {
         // If this is in an embedded TaskFragment and we want the dim applies on the TaskFragment.
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 33ef3c5..a9f0554 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -183,18 +183,21 @@
                 && (mWallpaperTarget == w || w.isDrawFinishedLw())) {
             if (DEBUG_WALLPAPER) Slog.v(TAG, "Found wallpaper target: " + w);
             mFindResults.setWallpaperTarget(w);
+            mFindResults.setIsWallpaperTargetForLetterbox(w.hasWallpaperForLetterboxBackground());
             if (w == mWallpaperTarget && w.isAnimating(TRANSITION | PARENTS)) {
                 // The current wallpaper target is animating, so we'll look behind it for
                 // another possible target and figure out what is going on later.
                 if (DEBUG_WALLPAPER) Slog.v(TAG,
                         "Win " + w + ": token animating, looking behind.");
             }
-            mFindResults.setIsWallpaperTargetForLetterbox(w.hasWallpaperForLetterboxBackground());
             // While the keyguard is going away, both notification shade and a normal activity such
             // as a launcher can satisfy criteria for a wallpaper target. In this case, we should
             // chose the normal activity, otherwise wallpaper becomes invisible when a new animation
             // starts before the keyguard going away animation finishes.
-            return w.mActivityRecord != null;
+            if (w.mActivityRecord == null && mDisplayContent.isKeyguardGoingAway()) {
+                return false;
+            }
+            return true;
         }
         return false;
     };
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 516d37c..22b690e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -51,6 +51,7 @@
 import com.android.internal.policy.KeyInterceptionInfo;
 import com.android.server.input.InputManagerService;
 import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
 
 import java.lang.annotation.Retention;
 import java.util.List;
@@ -1012,4 +1013,12 @@
      */
     public abstract void setOrientationRequestPolicy(boolean respected,
             int[] fromOrientations, int[] toOrientations);
+
+    /**
+     * Set whether screen capture should be disabled for all windows of a specific app windows based
+     * on sensitive content protections.
+     *
+     * @param packageInfos set of {@link PackageInfo} whose windows should be blocked from capture
+     */
+    public abstract void setShouldBlockScreenCaptureForApp(@NonNull Set<PackageInfo> packageInfos);
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 502912a..c63cc43 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -123,6 +123,7 @@
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
 import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
+import static com.android.server.wm.SensitiveContentPackages.PackageInfo;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
@@ -312,6 +313,7 @@
 import android.window.WindowContextInfo;
 
 import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.policy.IKeyguardDismissCallback;
@@ -366,6 +368,7 @@
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
@@ -1053,6 +1056,9 @@
 
     SystemPerformanceHinter mSystemPerformanceHinter;
 
+    @GuardedBy("mGlobalLock")
+    final SensitiveContentPackages mSensitiveContentPackages = new SensitiveContentPackages();
+
     /** Listener to notify activity manager about app transitions. */
     final WindowManagerInternal.AppTransitionListener mActivityManagerAppTransitionNotifier
             = new WindowManagerInternal.AppTransitionListener() {
@@ -1797,7 +1803,12 @@
 
             // Don't do layout here, the window must call
             // relayout to be displayed, so we'll do it there.
-            win.getParent().assignChildLayers();
+            if (win.mActivityRecord != null && win.mActivityRecord.isEmbedded()) {
+                // Assign child layers from the parent Task if the Activity is embedded.
+                win.getTask().assignChildLayers();
+            } else {
+                win.getParent().assignChildLayers();
+            }
 
             if (focusChanged) {
                 displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
@@ -1931,12 +1942,13 @@
 
     /**
      * Set whether screen capture is disabled for all windows of a specific user from
-     * the device policy cache.
+     * the device policy cache, or specific windows based on sensitive content protections.
      */
     @Override
     public void refreshScreenCaptureDisabled() {
         int callingUid = Binder.getCallingUid();
-        if (callingUid != SYSTEM_UID) {
+        // MY_UID (Process.myUid()) should always be SYSTEM_UID here, but using MY_UID for tests
+        if (callingUid != MY_UID) {
             throw new SecurityException("Only system can call refreshScreenCaptureDisabled.");
         }
 
@@ -7169,6 +7181,7 @@
             }
             mSystemPerformanceHinter.dump(pw, "");
             mTrustedPresentationListenerController.dump(pw);
+            mSensitiveContentPackages.dump(pw);
         }
     }
 
@@ -8550,6 +8563,14 @@
             InputTarget inputTarget = WindowManagerService.this.getInputTargetFromToken(inputToken);
             return inputTarget == null ? null : inputTarget.getWindowToken();
         }
+
+        @Override
+        public void setShouldBlockScreenCaptureForApp(Set<PackageInfo> packageInfos) {
+            synchronized (mGlobalLock) {
+                mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(packageInfos);
+                WindowManagerService.this.refreshScreenCaptureDisabled();
+            }
+        }
     }
 
     private final class ImeTargetVisibilityPolicyImpl extends ImeTargetVisibilityPolicy {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0b43be7..56f2bc3 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1896,6 +1896,14 @@
         if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0) {
             return true;
         }
+
+        if (com.android.server.notification.Flags.sensitiveNotificationAppProtection()) {
+            if (mWmService.mSensitiveContentPackages
+                    .shouldBlockScreenCaptureForApp(getOwningPackage(), getOwningUid())) {
+                return true;
+            }
+        }
+
         return !DevicePolicyCache.getInstance().isScreenCaptureAllowed(mShowUserId);
     }
 
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index dfa9dce..775570c 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -37,6 +37,7 @@
         "com_android_server_adb_AdbDebuggingManager.cpp",
         "com_android_server_am_BatteryStatsService.cpp",
         "com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
+        "com_android_server_BootReceiver.cpp",
         "com_android_server_ConsumerIrService.cpp",
         "com_android_server_companion_virtual_InputController.cpp",
         "com_android_server_devicepolicy_CryptoTestHelper.cpp",
@@ -94,6 +95,16 @@
     header_libs: [
         "bionic_libc_platform_headers",
     ],
+
+    static_libs: [
+        "libunwindstack",
+    ],
+
+    whole_static_libs: [
+        "libdebuggerd_tombstone_proto_to_text",
+    ],
+
+    runtime_libs: ["libdexfile"],
 }
 
 cc_defaults {
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index cc08488..15eb7c6 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -33,3 +33,7 @@
 
 # Bug component : 158088 = per-file *AnrTimer*
 per-file *AnrTimer* = file:/PERFORMANCE_OWNERS
+
+# Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java
+per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS
+per-file com_android_server_BootReceiver.cpp = file:/STABILITY_OWNERS
diff --git a/services/core/jni/com_android_server_BootReceiver.cpp b/services/core/jni/com_android_server_BootReceiver.cpp
new file mode 100644
index 0000000..3892d28
--- /dev/null
+++ b/services/core/jni/com_android_server_BootReceiver.cpp
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#include <libdebuggerd/tombstone.h>
+#include <nativehelper/JNIHelp.h>
+
+#include <sstream>
+
+#include "jni.h"
+#include "tombstone.pb.h"
+
+namespace android {
+
+static void writeToString(std::stringstream& ss, const std::string& line, bool should_log) {
+    ss << line << std::endl;
+}
+
+static jstring com_android_server_BootReceiver_getTombstoneText(JNIEnv* env, jobject,
+                                                                jbyteArray tombstoneBytes) {
+    Tombstone tombstone;
+    tombstone.ParseFromArray(env->GetByteArrayElements(tombstoneBytes, 0),
+                             env->GetArrayLength(tombstoneBytes));
+
+    std::stringstream tombstoneString;
+
+    tombstone_proto_to_text(tombstone,
+                            std::bind(&writeToString, std::ref(tombstoneString),
+                                      std::placeholders::_1, std::placeholders::_2));
+
+    return env->NewStringUTF(tombstoneString.str().c_str());
+}
+
+static const JNINativeMethod sMethods[] = {
+        /* name, signature, funcPtr */
+        {"getTombstoneText", "([B)Ljava/lang/String;",
+         (jstring*)com_android_server_BootReceiver_getTombstoneText},
+};
+
+int register_com_android_server_BootReceiver(JNIEnv* env) {
+    return jniRegisterNativeMethods(env, "com/android/server/BootReceiver", sMethods,
+                                    NELEM(sMethods));
+}
+
+} // namespace android
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
index 97b18fa..1e48ace 100644
--- a/services/core/jni/com_android_server_utils_AnrTimer.cpp
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -486,9 +486,11 @@
     void remove(AnrTimerService const* service) {
         AutoMutex _l(lock_);
         timer_id_t front = headTimerId();
-        for (auto i = running_.begin(); i != running_.end(); i++) {
+        for (auto i = running_.begin(); i != running_.end(); ) {
             if (i->service == service) {
-                running_.erase(i);
+                i = running_.erase(i);
+            } else {
+                i++;
             }
         }
         if (front != headTimerId()) restartLocked();
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index f3158d1..a4b1f84 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -52,10 +52,10 @@
 int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
 int register_android_server_SyntheticPasswordManager(JNIEnv* env);
 int register_android_hardware_display_DisplayViewport(JNIEnv* env);
-int register_android_server_utils_AnrTimer(JNIEnv *env);
 int register_android_server_am_OomConnection(JNIEnv* env);
 int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
 int register_android_server_am_LowMemDetector(JNIEnv* env);
+int register_android_server_utils_AnrTimer(JNIEnv *env);
 int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(JNIEnv* env);
 int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(JNIEnv* env);
 int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
@@ -66,6 +66,7 @@
 int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env);
 int register_android_server_companion_virtual_InputController(JNIEnv* env);
 int register_android_server_app_GameManagerService(JNIEnv* env);
+int register_com_android_server_BootReceiver(JNIEnv* env);
 int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
 int register_com_android_server_display_DisplayControl(JNIEnv* env);
 int register_com_android_server_SystemClockTime(JNIEnv* env);
@@ -114,10 +115,10 @@
     register_android_server_storage_AppFuse(env);
     register_android_server_SyntheticPasswordManager(env);
     register_android_hardware_display_DisplayViewport(env);
-    register_android_server_utils_AnrTimer(env);
     register_android_server_am_OomConnection(env);
     register_android_server_am_CachedAppOptimizer(env);
     register_android_server_am_LowMemDetector(env);
+    register_android_server_utils_AnrTimer(env);
     register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(env);
     register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(env);
     register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
@@ -128,6 +129,7 @@
     register_android_server_sensor_SensorService(vm, env);
     register_android_server_companion_virtual_InputController(env);
     register_android_server_app_GameManagerService(env);
+    register_com_android_server_BootReceiver(env);
     register_com_android_server_wm_TaskFpsCallbackController(env);
     register_com_android_server_display_DisplayControl(env);
     register_com_android_server_SystemClockTime(env);
diff --git a/services/java/com/android/server/SystemConfigService.java b/services/java/com/android/server/SystemConfigService.java
index 6e82907..fd21a32 100644
--- a/services/java/com/android/server/SystemConfigService.java
+++ b/services/java/com/android/server/SystemConfigService.java
@@ -21,6 +21,8 @@
 import android.Manifest;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
 import android.os.ISystemConfig;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -108,6 +110,15 @@
                     "Caller must hold " + Manifest.permission.QUERY_ALL_PACKAGES);
             return new ArrayList<>(SystemConfig.getInstance().getDefaultVrComponents());
         }
+
+        @Override
+        public List<String> getPreventUserDisablePackages() {
+            PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+            return SystemConfig.getInstance().getPreventUserDisablePackages().stream()
+                    .filter(preventUserDisablePackage ->
+                            pmi.canQueryPackage(Binder.getCallingUid(), preventUserDisablePackage))
+                    .collect(toList());
+        }
     };
 
     public SystemConfigService(Context context) {
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1185a4e..86ad494 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2965,6 +2965,12 @@
             t.traceEnd();
         }
 
+        if (com.android.server.notification.Flags.sensitiveNotificationAppProtection()) {
+            t.traceBegin("StartSensitiveContentProtectionManager");
+            mSystemServiceManager.startService(SensitiveContentProtectionManagerService.class);
+            t.traceEnd();
+        }
+
         // These are needed to propagate to the runnable below.
         final NetworkManagementService networkManagementF = networkManagement;
         final NetworkPolicyManagerService networkPolicyF = networkPolicy;
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
new file mode 100644
index 0000000..b363fd4
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.media.projection.MediaProjectionInfo;
+import android.media.projection.MediaProjectionManager;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.StatusBarNotification;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
+import android.testing.TestableLooper.RunWithLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public class SensitiveContentProtectionManagerServiceTest {
+    private static final String NOTIFICATION_KEY_1 = "com.android.server.notification.TEST_KEY_1";
+    private static final String NOTIFICATION_KEY_2 = "com.android.server.notification.TEST_KEY_2";
+
+    private static final String NOTIFICATION_PKG_1 = "com.android.server.notification.one";
+    private static final String NOTIFICATION_PKG_2 = "com.android.server.notification.two";
+
+    private static final int NOTIFICATION_UID_1 = 5;
+    private static final int NOTIFICATION_UID_2 = 6;
+
+    @Rule
+    public final TestableContext mContext =
+            new TestableContext(getInstrumentation().getTargetContext(), null);
+
+    private SensitiveContentProtectionManagerService mSensitiveContentProtectionManagerService;
+
+    @Captor
+    ArgumentCaptor<MediaProjectionManager.Callback> mMediaProjectionCallbackCaptor;
+
+    @Mock
+    private MediaProjectionManager mProjectionManager;
+
+    @Mock
+    private WindowManagerInternal mWindowManager;
+
+    @Mock
+    private StatusBarNotification mNotification1;
+
+    @Mock
+    private StatusBarNotification mNotification2;
+
+    @Mock
+    private RankingMap mRankingMap;
+
+    @Mock
+    private Ranking mSensitiveRanking;
+
+    @Mock
+    private Ranking mNonSensitiveRanking;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mSensitiveContentProtectionManagerService =
+                new SensitiveContentProtectionManagerService(mContext);
+
+        mSensitiveContentProtectionManagerService.mNotificationListener =
+                spy(mSensitiveContentProtectionManagerService.mNotificationListener);
+
+        // Setup RankingMap and two possilbe rankings
+        when(mSensitiveRanking.hasSensitiveContent()).thenReturn(true);
+        when(mNonSensitiveRanking.hasSensitiveContent()).thenReturn(false);
+        doReturn(mRankingMap)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getCurrentRanking();
+
+        setupSensitiveNotification();
+
+        mSensitiveContentProtectionManagerService.init(mProjectionManager, mWindowManager);
+
+        // Obtain useful mMediaProjectionCallback
+        verify(mProjectionManager).addCallback(mMediaProjectionCallbackCaptor.capture(), any());
+    }
+
+    @After
+    public void tearDown() {
+        mSensitiveContentProtectionManagerService.onDestroy();
+    }
+
+    private Set<PackageInfo> setupSensitiveNotification() {
+        // Setup Notification Values
+        when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
+        when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+        when(mNotification1.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+        when(mNotification2.getKey()).thenReturn(NOTIFICATION_KEY_2);
+        when(mNotification2.getPackageName()).thenReturn(NOTIFICATION_PKG_2);
+        when(mNotification2.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+        StatusBarNotification[] mNotifications =
+                new StatusBarNotification[] {mNotification1, mNotification2};
+        doReturn(mNotifications)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getActiveNotifications();
+
+        when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1)))
+                .thenReturn(mSensitiveRanking);
+        when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
+                .thenReturn(mNonSensitiveRanking);
+
+        return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1));
+    }
+
+    private Set<PackageInfo> setupMultipleSensitiveNotificationsFromSamePackageAndUid() {
+        // Setup Notification Values
+        when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
+        when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+        when(mNotification1.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+        when(mNotification2.getKey()).thenReturn(NOTIFICATION_KEY_2);
+        when(mNotification2.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+        when(mNotification2.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+        StatusBarNotification[] mNotifications =
+                new StatusBarNotification[] {mNotification1, mNotification2};
+        doReturn(mNotifications)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getActiveNotifications();
+
+        when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1)))
+                .thenReturn(mSensitiveRanking);
+        when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
+                .thenReturn(mSensitiveRanking);
+
+        return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1));
+    }
+
+    private Set<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentPackage() {
+        // Setup Notification Values
+        when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
+        when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+        when(mNotification1.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+        when(mNotification2.getKey()).thenReturn(NOTIFICATION_KEY_2);
+        when(mNotification2.getPackageName()).thenReturn(NOTIFICATION_PKG_2);
+        when(mNotification2.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+        StatusBarNotification[] mNotifications =
+                new StatusBarNotification[] {mNotification1, mNotification2};
+        doReturn(mNotifications)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getActiveNotifications();
+
+        when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1)))
+                .thenReturn(mSensitiveRanking);
+        when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
+                .thenReturn(mSensitiveRanking);
+
+        return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
+                new PackageInfo(NOTIFICATION_PKG_2, NOTIFICATION_UID_1));
+    }
+
+    private Set<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentUid() {
+        // Setup Notification Values
+        when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
+        when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+        when(mNotification1.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+        when(mNotification2.getKey()).thenReturn(NOTIFICATION_KEY_2);
+        when(mNotification2.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+        when(mNotification2.getUid()).thenReturn(NOTIFICATION_UID_2);
+
+        StatusBarNotification[] mNotifications =
+                new StatusBarNotification[] {mNotification1, mNotification2};
+        doReturn(mNotifications)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getActiveNotifications();
+
+        when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1)))
+                .thenReturn(mSensitiveRanking);
+        when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
+                .thenReturn(mSensitiveRanking);
+
+        return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
+                new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_2));
+    }
+
+    private void setupNoSensitiveNotifications() {
+        // Setup Notification Values
+        when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
+        when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
+        when(mNotification1.getUid()).thenReturn(NOTIFICATION_UID_1);
+
+        StatusBarNotification[] mNotifications = new StatusBarNotification[] {mNotification1};
+        doReturn(mNotifications)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getActiveNotifications();
+
+        when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1)))
+                .thenReturn(mNonSensitiveRanking);
+    }
+
+    private void setupNoNotifications() {
+        // Setup Notification Values
+        StatusBarNotification[] mNotifications = new StatusBarNotification[] {};
+        doReturn(mNotifications)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getActiveNotifications();
+    }
+
+    @Test
+    public void mediaProjectionOnStart_onProjectionStart_setWmBlockedPackages() {
+        Set<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+        verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+    }
+
+    @Test
+    public void mediaProjectionOnStart_noSensitiveNotifications_noBlockedPackages() {
+        setupNoSensitiveNotifications();
+
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+    }
+
+    @Test
+    public void mediaProjectionOnStart_noNotifications_noBlockedPackages() {
+        setupNoNotifications();
+
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+    }
+
+    @Test
+    public void mediaProjectionOnStart_multipleNotifications_setWmBlockedPackages() {
+        Set<PackageInfo> expectedBlockedPackages =
+                setupMultipleSensitiveNotificationsFromSamePackageAndUid();
+
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+        verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+    }
+
+    @Test
+    public void mediaProjectionOnStart_multiplePackages_setWmBlockedPackages() {
+        Set<PackageInfo> expectedBlockedPackages =
+                setupMultipleSensitiveNotificationsFromDifferentPackage();
+
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+        verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+    }
+
+    @Test
+    public void mediaProjectionOnStart_multipleUid_setWmBlockedPackages() {
+        Set<PackageInfo> expectedBlockedPackages =
+                setupMultipleSensitiveNotificationsFromDifferentUid();
+
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+        verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+    }
+
+    @Test
+    public void mediaProjectionOnStop_onProjectionEnd_clearWmBlockedPackages() {
+        setupSensitiveNotification();
+
+        MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class);
+        mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
+        Mockito.reset(mWindowManager);
+
+        mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
+
+        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+    }
+
+    @Test
+    public void mediaProjectionOnStart_afterOnStop_onProjectionStart_setWmBlockedPackages() {
+        Set<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+
+        MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class);
+        mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
+        mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
+        Mockito.reset(mWindowManager);
+
+        mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
+
+        verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+    }
+
+    @Test
+    public void mediaProjectionOnStart_getActiveNotificationsThrows_noBlockedPackages() {
+        doThrow(SecurityException.class)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getActiveNotifications();
+
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+    }
+
+    @Test
+    public void mediaProjectionOnStart_getCurrentRankingThrows_noBlockedPackages() {
+        doThrow(SecurityException.class)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getCurrentRanking();
+
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+    }
+
+    @Test
+    public void mediaProjectionOnStart_getCurrentRanking_nullRankingMap_noBlockedPackages() {
+        doReturn(null)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getCurrentRanking();
+
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+    }
+
+    @Test
+    public void mediaProjectionOnStart_getCurrentRanking_missingRanking_noBlockedPackages() {
+        when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
+
+        doReturn(mRankingMap)
+                .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+                .getCurrentRanking();
+
+        mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+
+        verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
index 60cedcf..24e7242 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java
@@ -108,6 +108,20 @@
     }
 
     @Test
+    public void testPackageMonitorCallback_SuspendNoAccessCallbackNotCalled() throws Exception {
+        BiFunction<Integer, Bundle, Bundle> filterExtras = (callingUid, intentExtras) -> null;
+
+        IRemoteCallback callback = createMockPackageMonitorCallback();
+        mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */,
+                Binder.getCallingUid());
+        mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGES_SUSPENDED,
+                FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}, null /* instantUserIds */,
+                null /* broadcastAllowList */, mHandler, filterExtras);
+
+        verify(callback, after(WAIT_CALLBACK_CALLED_IN_MS).never()).sendResult(any());
+    }
+
+    @Test
     public void testPackageMonitorCallback_SuspendCallbackCalled() throws Exception {
         Bundle result = new Bundle();
         result.putInt(Intent.EXTRA_UID, FAKE_PACKAGE_UID);
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index c42c735..97e94e3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -32,6 +32,7 @@
 import android.Manifest;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.ITrustListener;
 import android.app.trust.ITrustManager;
@@ -45,7 +46,9 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricManager;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -53,7 +56,12 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
+import android.security.Authorization;
+import android.security.authorization.IKeystoreAuthorization;
 import android.service.trust.TrustAgentService;
 import android.testing.TestableContext;
 import android.view.IWindowManager;
@@ -83,23 +91,34 @@
 
     @Rule
     public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+            .spyStatic(ActivityManager.class)
+            .spyStatic(Authorization.class)
             .mockStatic(ServiceManager.class)
             .mockStatic(WindowManagerGlobal.class)
             .build();
 
     @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Rule
     public final MockContext mMockContext = new MockContext(
             ApplicationProvider.getApplicationContext());
 
     private static final String URI_SCHEME_PACKAGE = "package";
     private static final int TEST_USER_ID = 50;
+    private static final int PARENT_USER_ID = 60;
+    private static final int PROFILE_USER_ID = 70;
+    private static final long[] PARENT_BIOMETRIC_SIDS = new long[] { 600L, 601L };
+    private static final long[] PROFILE_BIOMETRIC_SIDS = new long[] { 700L, 701L };
 
     private final ArrayList<ResolveInfo> mTrustAgentResolveInfoList = new ArrayList<>();
     private final ArrayList<ComponentName> mKnownTrustAgents = new ArrayList<>();
     private final ArrayList<ComponentName> mEnabledTrustAgents = new ArrayList<>();
 
     private @Mock ActivityManager mActivityManager;
+    private @Mock BiometricManager mBiometricManager;
     private @Mock DevicePolicyManager mDevicePolicyManager;
+    private @Mock IKeystoreAuthorization mKeystoreAuthorization;
     private @Mock LockPatternUtils mLockPatternUtils;
     private @Mock PackageManager mPackageManager;
     private @Mock UserManager mUserManager;
@@ -113,6 +132,9 @@
     @Before
     public void setUp() throws Exception {
         when(mActivityManager.isUserRunning(TEST_USER_ID)).thenReturn(true);
+        doReturn(mock(IActivityManager.class)).when(() -> ActivityManager.getService());
+
+        doReturn(mKeystoreAuthorization).when(() -> Authorization.getService());
 
         when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
         when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
@@ -146,6 +168,7 @@
         when(mWindowManager.isKeyguardLocked()).thenReturn(true);
 
         mMockContext.addMockSystemService(ActivityManager.class, mActivityManager);
+        mMockContext.addMockSystemService(BiometricManager.class, mBiometricManager);
         mMockContext.setMockPackageManager(mPackageManager);
         mMockContext.addMockSystemService(UserManager.class, mUserManager);
         doReturn(mWindowManager).when(() -> WindowManagerGlobal.getWindowManagerService());
@@ -322,6 +345,73 @@
         verify(trustListener).onEnabledTrustAgentsChanged(TEST_USER_ID);
     }
 
+    // Tests that when the device is locked for a managed profile with a *unified* challenge, the
+    // device locked notification that is sent to Keystore contains the biometric SIDs of the parent
+    // user, not the profile.  This matches the authentication that is needed to unlock the device
+    // for the profile again.
+    @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+    public void testLockDeviceForManagedProfileWithUnifiedChallenge_usesParentBiometricSids()
+            throws Exception {
+        setupMocksForProfile(/* unifiedChallenge= */ true);
+
+        when(mWindowManager.isKeyguardLocked()).thenReturn(false);
+        mTrustManager.reportKeyguardShowingChanged();
+        verify(mKeystoreAuthorization).onDeviceUnlocked(PARENT_USER_ID, null);
+        verify(mKeystoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
+
+        when(mWindowManager.isKeyguardLocked()).thenReturn(true);
+        mTrustManager.reportKeyguardShowingChanged();
+        verify(mKeystoreAuthorization)
+                .onDeviceLocked(eq(PARENT_USER_ID), eq(PARENT_BIOMETRIC_SIDS));
+        verify(mKeystoreAuthorization)
+                .onDeviceLocked(eq(PROFILE_USER_ID), eq(PARENT_BIOMETRIC_SIDS));
+    }
+
+    // Tests that when the device is locked for a managed profile with a *separate* challenge, the
+    // device locked notification that is sent to Keystore contains the biometric SIDs of the
+    // profile itself.  This matches the authentication that is needed to unlock the device for the
+    // profile again.
+    @Test
+    public void testLockDeviceForManagedProfileWithSeparateChallenge_usesProfileBiometricSids()
+            throws Exception {
+        setupMocksForProfile(/* unifiedChallenge= */ false);
+
+        mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, false);
+        verify(mKeystoreAuthorization).onDeviceUnlocked(PROFILE_USER_ID, null);
+
+        mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, true);
+        verify(mKeystoreAuthorization)
+                .onDeviceLocked(eq(PROFILE_USER_ID), eq(PROFILE_BIOMETRIC_SIDS));
+    }
+
+    private void setupMocksForProfile(boolean unifiedChallenge) {
+        UserInfo parent = new UserInfo(PARENT_USER_ID, "parent", UserInfo.FLAG_FULL);
+        UserInfo profile = new UserInfo(PROFILE_USER_ID, "profile", UserInfo.FLAG_MANAGED_PROFILE);
+        when(mUserManager.getAliveUsers()).thenReturn(List.of(parent, profile));
+        when(mUserManager.getUserInfo(PARENT_USER_ID)).thenReturn(parent);
+        when(mUserManager.getUserInfo(PROFILE_USER_ID)).thenReturn(profile);
+        when(mUserManager.getProfileParent(PROFILE_USER_ID)).thenReturn(parent);
+        when(mUserManager.getEnabledProfileIds(PARENT_USER_ID))
+                .thenReturn(new int[] { PROFILE_USER_ID });
+
+        when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+        when(mLockPatternUtils.isProfileWithUnifiedChallenge(PROFILE_USER_ID))
+                .thenReturn(unifiedChallenge);
+        when(mLockPatternUtils.isManagedProfileWithUnifiedChallenge(PROFILE_USER_ID))
+                .thenReturn(unifiedChallenge);
+        when(mLockPatternUtils.isSeparateProfileChallengeEnabled(PROFILE_USER_ID))
+                .thenReturn(!unifiedChallenge);
+
+        when(mBiometricManager.getAuthenticatorIds(PARENT_USER_ID))
+                .thenReturn(PARENT_BIOMETRIC_SIDS);
+        when(mBiometricManager.getAuthenticatorIds(PROFILE_USER_ID))
+                .thenReturn(PROFILE_BIOMETRIC_SIDS);
+
+        bootService();
+        mService.onUserSwitching(null, new SystemService.TargetUser(parent));
+    }
+
     private void addTrustAgent(ComponentName agentComponentName, boolean isSystemApp) {
         ApplicationInfo applicationInfo = new ApplicationInfo();
         if (isSystemApp) {
@@ -378,6 +468,11 @@
                     scheduler);
         }
 
+        @Override
+        public void sendBroadcastAsUser(Intent intent, UserHandle user,
+                @Nullable String receiverPermission, @Nullable Bundle options) {
+        }
+
         void sendPackageChangedBroadcast(ComponentName changedComponent) {
             Intent intent = new Intent(
                     Intent.ACTION_PACKAGE_CHANGED,
diff --git a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
new file mode 100644
index 0000000..523c5c0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.test.AndroidTestCase;
+
+import com.android.server.os.TombstoneProtos;
+import com.android.server.os.TombstoneProtos.Tombstone;
+
+public class BootReceiverTest extends AndroidTestCase {
+    private static final String TAG = "BootReceiverTest";
+
+    public void testRemoveMemoryFromTombstone() {
+        Tombstone tombstoneBase = Tombstone.newBuilder()
+                .setBuildFingerprint("build_fingerprint")
+                .setRevision("revision")
+                .setPid(123)
+                .setTid(23)
+                .setUid(34)
+                .setSelinuxLabel("selinux_label")
+                .addCommandLine("cmd1")
+                .addCommandLine("cmd2")
+                .addCommandLine("cmd3")
+                .setProcessUptime(300)
+                .setAbortMessage("abort")
+                .addCauses(TombstoneProtos.Cause.newBuilder()
+                        .setHumanReadable("cause1")
+                        .setMemoryError(TombstoneProtos.MemoryError.newBuilder()
+                                .setTool(TombstoneProtos.MemoryError.Tool.SCUDO)
+                                .setType(TombstoneProtos.MemoryError.Type.DOUBLE_FREE)))
+                .addLogBuffers(TombstoneProtos.LogBuffer.newBuilder().setName("name").addLogs(
+                        TombstoneProtos.LogMessage.newBuilder()
+                                .setTimestamp("123")
+                                .setMessage("message")))
+                .addOpenFds(TombstoneProtos.FD.newBuilder().setFd(1).setPath("path"))
+                .build();
+
+        Tombstone tombstoneWithoutMemory = tombstoneBase.toBuilder()
+                .putThreads(1, TombstoneProtos.Thread.newBuilder()
+                        .setId(1)
+                        .setName("thread1")
+                        .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
+                        .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
+                        .addBacktraceNote("backtracenote1")
+                        .addUnreadableElfFiles("files1")
+                        .setTaggedAddrCtrl(1)
+                        .setPacEnabledKeys(10)
+                        .build())
+                .build();
+
+        Tombstone tombstoneWithMemory = tombstoneBase.toBuilder()
+                .addMemoryMappings(TombstoneProtos.MemoryMapping.newBuilder()
+                        .setBeginAddress(1)
+                        .setEndAddress(100)
+                        .setOffset(10)
+                        .setRead(true)
+                        .setWrite(true)
+                        .setExecute(false)
+                        .setMappingName("mapping")
+                        .setBuildId("build")
+                        .setLoadBias(70))
+                .putThreads(1, TombstoneProtos.Thread.newBuilder()
+                        .setId(1)
+                        .setName("thread1")
+                        .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
+                        .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
+                        .addBacktraceNote("backtracenote1")
+                        .addUnreadableElfFiles("files1")
+                        .addMemoryDump(TombstoneProtos.MemoryDump.newBuilder()
+                                .setRegisterName("register1")
+                                .setMappingName("mapping")
+                                .setBeginAddress(10))
+                        .setTaggedAddrCtrl(1)
+                        .setPacEnabledKeys(10)
+                        .build())
+                .build();
+
+        assertThat(BootReceiver.removeMemoryFromTombstone(tombstoneWithMemory))
+                .isEqualTo(tombstoneWithoutMemory);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 995d1f4..276c832 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -1982,8 +1982,8 @@
         return new AssociationInfo(associationId, /* userId= */ 0, /* packageName=*/ null,
                 /* tag= */ null, MacAddress.BROADCAST_ADDRESS, /* displayName= */ "", deviceProfile,
                 /* associatedDevice= */ null, /* selfManaged= */ true,
-                /* notifyOnDeviceNearby= */ false, /* revoked= */false,  /* timeApprovedMs= */0,
-                /* lastTimeConnectedMs= */0, /* systemDataSyncFlags= */ -1);
+                /* notifyOnDeviceNearby= */ false, /* revoked= */ false, /* pending= */ false,
+                /* timeApprovedMs= */0, /* lastTimeConnectedMs= */0, /* systemDataSyncFlags= */ -1);
     }
 
     /** Helper class to drop permissions temporarily and restore them at the end of a test. */
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
index 6a088d9..757abde 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
@@ -157,7 +157,7 @@
         validateTagCount("uses-library", 1000, tag)
         validateTagCount("activity-alias", 4000, tag)
         validateTagCount("provider", 8000, tag)
-        validateTagCount("activity", 40000, tag)
+        validateTagCount("activity", 30000, tag)
     }
 
     @Test
@@ -465,7 +465,8 @@
             R.styleable.AndroidManifestData_pathAdvancedPattern,
             4000
         )
-        validateTagAttr(tag, "mimeType", R.styleable.AndroidManifestData_mimeType, 512)
+        validateTagAttr(tag, "mimeType", R.styleable.AndroidManifestData_mimeType, 255)
+        validateTagAttr(tag, "mimeGroup", R.styleable.AndroidManifestData_mimeGroup, 1024)
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index cf8548c..1b77b99 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -19,17 +19,21 @@
 import static android.app.Notification.GROUP_ALERT_ALL;
 import static android.app.Notification.GROUP_ALERT_CHILDREN;
 import static android.app.Notification.GROUP_ALERT_SUMMARY;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION;
 import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -195,7 +199,8 @@
         // TODO (b/291907312): remove feature flag
         mSetFlagsRule.enableFlags(Flags.FLAG_REFACTOR_ATTENTION_HELPER);
         // Disable feature flags by default. Tests should enable as needed.
-        mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS, Flags.FLAG_EXPIRE_BITMAPS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS,
+                Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS, Flags.FLAG_VIBRATE_WHILE_UNLOCKED);
 
         mService = spy(new NotificationManagerService(getContext(), mNotificationRecordLogger,
             mNotificationInstanceIdSequence));
@@ -364,10 +369,20 @@
     }
 
     private NotificationRecord getNotificationRecord(int id,
-        boolean insistent, boolean once,
-        boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
-        boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
-        boolean isLeanback, UserHandle userHandle) {
+            boolean insistent, boolean once,
+            boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
+            boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
+            boolean isLeanback, UserHandle userHandle) {
+        return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, defaultVibration,
+                defaultSound, defaultLights, groupKey, groupAlertBehavior, isLeanback, userHandle,
+                mPkg);
+    }
+
+    private NotificationRecord getNotificationRecord(int id,
+            boolean insistent, boolean once,
+            boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
+            boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
+            boolean isLeanback, UserHandle userHandle, String packageName) {
 
         final Builder builder = new Builder(getContext())
             .setContentTitle("foo")
@@ -427,8 +442,8 @@
         when(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
             .thenReturn(isLeanback);
 
-        StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
-            mPid, n, userHandle, null, System.currentTimeMillis());
+        StatusBarNotification sbn = new StatusBarNotification(packageName, packageName, id, mTag,
+                mUid, mPid, n, userHandle, null, System.currentTimeMillis());
         NotificationRecord r = new NotificationRecord(context, sbn, mChannel);
         mService.addNotification(r);
         return r;
@@ -1990,7 +2005,6 @@
     public void testBeepVolume_politeNotif() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
         TestableFlagResolver flagResolver = new TestableFlagResolver();
-        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
         initAttentionHelper(flagResolver);
@@ -2015,13 +2029,11 @@
         assertNotEquals(-1, r.getLastAudiblyAlertedMs());
     }
 
-    // TODO b/270456865: Only one of the two strategies will be released.
-    //  The other one need to be removed
     @Test
-    public void testBeepVolume_politeNotif_Strategy2() throws Exception {
+    public void testBeepVolume_politeNotif_GlobalStrategy() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
         TestableFlagResolver flagResolver = new TestableFlagResolver();
-        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule2");
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
         initAttentionHelper(flagResolver);
@@ -2032,14 +2044,58 @@
         mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
         Mockito.reset(mRingtonePlayer);
 
-        // update should beep at 0% volume
-        r.isUpdate = true;
-        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        // Use different package for next notifications
+        NotificationRecord r2 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+                true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+                false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
+
+        // update should beep at 50% volume
+        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+        verifyBeepVolume(0.5f);
+
+        // Use different package for next notifications
+        NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+                true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+                false, null, Notification.GROUP_ALERT_ALL, false, mUser, "yetAnotherPkg");
+
+        // 2nd update should beep at 0% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
         verifyBeepVolume(0.0f);
 
+        verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
+    @Test
+    public void testBeepVolume_politeNotif_GlobalStrategy_ChannelHasUserSound() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        initAttentionHelper(flagResolver);
+
+        NotificationRecord r = getBeepyNotification();
+
+        // set up internal state
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        Mockito.reset(mRingtonePlayer);
+
+        // Use package with user-set sounds for next notifications
+        mChannel = new NotificationChannel("test2", "test2", IMPORTANCE_DEFAULT);
+        mChannel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
+        NotificationRecord r2 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+                true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+                false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
+
+        // update should beep at 100% volume
+        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+        verifyBeepVolume(1.0f);
+
         // 2nd update should beep at 50% volume
         Mockito.reset(mRingtonePlayer);
-        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
         verifyBeepVolume(0.5f);
 
         verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt());
@@ -2047,10 +2103,101 @@
     }
 
     @Test
+    public void testBeepVolume_politeNotif_applyPerApp() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        // NOTIFICATION_COOLDOWN_ALL setting is enabled
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+        initAttentionHelper(flagResolver);
+
+        NotificationRecord r = getBeepyNotification();
+
+        // set up internal state
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        Mockito.reset(mRingtonePlayer);
+
+        // Use different channel for next notifications
+        mChannel = new NotificationChannel("test2", "test2", IMPORTANCE_DEFAULT);
+
+        // update should beep at 50% volume
+        NotificationRecord r2 = getBeepyNotification();
+        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+        verifyBeepVolume(0.5f);
+
+        // 2nd update should beep at 0% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+        verifyBeepVolume(0.0f);
+
+        // Use different package for next notifications
+        NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+                true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+                false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
+
+        // Update from new package should beep at 100% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+        verifyBeepVolume(1.0f);
+
+        verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
+    @Test
+    public void testBeepVolume_politeNotif_applyPerApp_ChannelHasUserSound() throws Exception {
+        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS);
+        TestableFlagResolver flagResolver = new TestableFlagResolver();
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
+        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
+        // NOTIFICATION_COOLDOWN_ALL setting is enabled
+        Settings.System.putInt(getContext().getContentResolver(),
+                Settings.System.NOTIFICATION_COOLDOWN_ALL, 1);
+        initAttentionHelper(flagResolver);
+
+        NotificationRecord r = getBeepyNotification();
+
+        // set up internal state
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        Mockito.reset(mRingtonePlayer);
+
+        // Use different channel for next notifications
+        mChannel = new NotificationChannel("test2", "test2", IMPORTANCE_DEFAULT);
+        mChannel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
+
+        // update should beep at 100% volume
+        NotificationRecord r2 = getBeepyNotification();
+        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+        verifyBeepVolume(1.0f);
+
+        // 2nd update should beep at 50% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS);
+        verifyBeepVolume(0.5f);
+
+        // Use different package for next notifications
+        mChannel = new NotificationChannel("test3", "test3", IMPORTANCE_DEFAULT);
+        NotificationRecord r3 = getNotificationRecord(mId, false /* insistent */, false /* once */,
+                true /* noisy */, false /* buzzy*/, false /* lights */, true, true,
+                false, null, Notification.GROUP_ALERT_ALL, false, mUser, "anotherPkg");
+
+        // Update from new package should beep at 100% volume
+        Mockito.reset(mRingtonePlayer);
+        mAttentionHelper.buzzBeepBlinkLocked(r3, DEFAULT_SIGNALS);
+        verifyBeepVolume(1.0f);
+
+        verify(mAccessibilityService, times(4)).sendAccessibilityEvent(any(), anyInt());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+    }
+
+    @Test
     public void testVibrationIntensity_politeNotif() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
         TestableFlagResolver flagResolver = new TestableFlagResolver();
-        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
         initAttentionHelper(flagResolver);
@@ -2076,37 +2223,9 @@
     }
 
     @Test
-    public void testVibrationIntensity_politeNotif_Strategy2() throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
-        TestableFlagResolver flagResolver = new TestableFlagResolver();
-        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule2");
-        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
-        flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
-        initAttentionHelper(flagResolver);
-
-        NotificationRecord r = getBuzzyBeepyNotification();
-
-        // set up internal state
-        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
-
-        VibratorHelper vibratorHelper = mAttentionHelper.getVibratorHelper();
-        Mockito.reset(vibratorHelper);
-
-        // update should buzz at 0% intensity
-        r.isUpdate = true;
-        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
-
-        verify(vibratorHelper, times(1)).scale(any(), eq(0.0f));
-        Mockito.reset(vibratorHelper);
-
-        // 2nd update should buzz at 50% intensity
-        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
-        verify(vibratorHelper, times(1)).scale(any(), eq(0.5f));
-    }
-
-    @Test
     public void testBuzzOnlyOnScreenUnlock_politeNotif() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
+        mSetFlagsRule.enableFlags(Flags.FLAG_VIBRATE_WHILE_UNLOCKED);
         TestableFlagResolver flagResolver = new TestableFlagResolver();
 
         // When NOTIFICATION_COOLDOWN_VIBRATE_UNLOCKED setting is enabled
@@ -2161,7 +2280,6 @@
     public void testBeepVolume_politeNotif_workProfile() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
         TestableFlagResolver flagResolver = new TestableFlagResolver();
-        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
 
@@ -2202,7 +2320,6 @@
     public void testBeepVolume_politeNotif_workProfile_disabled() throws Exception {
         mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS);
         TestableFlagResolver flagResolver = new TestableFlagResolver();
-        flagResolver.setFlagOverride(NotificationFlags.NOTIF_COOLDOWN_RULE, "rule1");
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50);
         flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
index 5892793..c10c3c2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java
@@ -170,6 +170,10 @@
         Settings.Secure.putIntForUser(getContext().getContentResolver(),
                 Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, USER_SYSTEM);
         mHistoryManager.mSettingsObserver.update(null, USER_SYSTEM);
+        // fake cloned settings to profile
+        Settings.Secure.putIntForUser(getContext().getContentResolver(),
+                Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, mProfileId);
+        mHistoryManager.mSettingsObserver.update(null, mProfileId);
 
         // unlock user, verify that history is disabled for self and profile
         mHistoryManager.onUserUnlocked(USER_SYSTEM);
@@ -181,6 +185,37 @@
     }
 
     @Test
+    public void testAddProfile_historyEnabledInPrimary() {
+        // create a history
+        mHistoryManager.onUserUnlocked(MIN_SECONDARY_USER_ID);
+        assertThat(mHistoryManager.doesHistoryExistForUser(MIN_SECONDARY_USER_ID)).isTrue();
+
+        // fake Settings#CLONE_TO_MANAGED_PROFILE
+        int newProfileId = 99;
+        Settings.Secure.putIntForUser(getContext().getContentResolver(),
+                Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 1, newProfileId);
+        mUsers = new ArrayList<>();
+        UserInfo userFullSecondary = new UserInfo();
+        userFullSecondary.id = MIN_SECONDARY_USER_ID;
+        mUsers.add(userFullSecondary);
+        UserInfo userProfile = new UserInfo();
+        userProfile.id = newProfileId;
+        mUsers.add(userProfile);
+        when(mUserManager.getUsers()).thenReturn(mUsers);
+
+        mProfiles = new int[] {MIN_SECONDARY_USER_ID, userProfile.id};
+        when(mUserManager.getProfileIds(MIN_SECONDARY_USER_ID, true)).thenReturn(mProfiles);
+        when(mUserManager.getProfileIds(userProfile.id, true))
+                .thenReturn(new int[] {userProfile.id});
+        when(mUserManager.getProfileParent(userProfile.id)).thenReturn(userFullSecondary);
+
+        // add profile
+        mHistoryManager.onUserAdded(newProfileId);
+        mHistoryManager.onUserUnlocked(newProfileId);
+        assertThat(mHistoryManager.doesHistoryExistForUser(newProfileId)).isTrue();
+    }
+
+    @Test
     public void testOnUserUnlocked_historyDisabledThenEnabled() {
         // create a history
         mHistoryManager.onUserUnlocked(USER_SYSTEM);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index f1edd9a..c1f35cc 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -958,22 +958,24 @@
         mTestNotificationChannel.setAllowBubbles(channelEnabled);
     }
 
-    private void setUpPrefsForHistory(int uid, boolean globalEnabled) throws Exception {
+    private void setUpPrefsForHistory(@UserIdInt int userId, boolean globalEnabled)
+            throws Exception {
         initNMS(SystemService.PHASE_ACTIVITY_MANAGER_READY);
 
         // Sets NOTIFICATION_HISTORY_ENABLED setting for calling process uid
         Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0, uid);
+                Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0, userId);
         // Sets NOTIFICATION_HISTORY_ENABLED setting for uid 0
         Settings.Secure.putInt(mContext.getContentResolver(),
                 Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0);
+        setUsers(new int[] {0, userId});
 
         // Forces an update by calling observe on mSettingsObserver, which picks up the settings
         // changes above.
         mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START, mMainLooper);
 
         assertEquals(globalEnabled, Settings.Secure.getIntForUser(mContext.getContentResolver(),
-                Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0 /* =def */, uid) != 0);
+                Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0 /* =def */, userId) != 0);
     }
 
     private StatusBarNotification generateSbn(String pkg, int uid, long postTime, int userId) {
@@ -10443,7 +10445,7 @@
     @Test
     public void testHandleOnPackageRemoved_ClearsHistory() throws Exception {
         // Enables Notification History setting
-        setUpPrefsForHistory(mUid, true /* =enabled */);
+        setUpPrefsForHistory(mUserId, true /* =enabled */);
 
         // Posts a notification to the mTestNotificationChannel.
         final NotificationRecord notif = generateNotificationRecord(
diff --git a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
new file mode 100644
index 0000000..c3da903
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.policy;
+
+import static android.os.PowerManager.WAKE_REASON_CAMERA_LAUNCH;
+import static android.os.PowerManager.WAKE_REASON_LID;
+import static android.os.PowerManager.WAKE_REASON_GESTURE;
+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.InputDevice.SOURCE_ROTARY_ENCODER;
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
+import static android.view.KeyEvent.KEYCODE_HOME;
+import static android.view.KeyEvent.KEYCODE_POWER;
+import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY;
+
+import static com.android.internal.R.bool.config_allowTheaterModeWakeFromKey;
+import static com.android.internal.R.bool.config_allowTheaterModeWakeFromPowerKey;
+import static com.android.internal.R.bool.config_allowTheaterModeWakeFromMotion;
+import static com.android.internal.R.bool.config_allowTheaterModeWakeFromCameraLens;
+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.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.os.PowerManager;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.os.Clock;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.LocalServices;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.function.BooleanSupplier;
+/**
+ * Test class for {@link WindowWakeUpPolicy}.
+ *
+ * <p>Build/Install/Run: atest WmTests:WindowWakeUpPolicyTests
+ */
+public final class WindowWakeUpPolicyTests {
+    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Mock PowerManager mPowerManager;
+    @Mock Clock mClock;
+    @Mock WindowWakeUpPolicyInternal.InputWakeUpDelegate mInputWakeUpDelegate;
+
+    private Context mContextSpy;
+    private Resources mResourcesSpy;
+
+    private WindowWakeUpPolicy mPolicy;
+
+    @Before
+    public void setUp() {
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+        mResourcesSpy = spy(mContextSpy.getResources());
+        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
+        LocalServices.removeServiceForTest(WindowWakeUpPolicyInternal.class);
+    }
+
+    @Test
+    public void testSupportsInputWakeDelegatse_publishesLocalService() {
+        mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+
+        mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+        assertThat(LocalServices.getService(WindowWakeUpPolicyInternal.class)).isNotNull();
+    }
+
+    @Test
+    public void testDoesNotSupportInputWakeDelegatse_doesNotPublishLocalService() {
+        mSetFlagsRule.disableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+
+        mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+        assertThat(LocalServices.getService(WindowWakeUpPolicyInternal.class)).isNull();
+    }
+
+    @Test
+    public void testMotionWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
+        setTheaterModeEnabled(false);
+        mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+        mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+        LocalServices.getService(WindowWakeUpPolicyInternal.class)
+                .setInputWakeUpDelegate(mInputWakeUpDelegate);
+
+        setDelegatedMotionWakeUpResult(true);
+
+        // 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();
+        verify(mInputWakeUpDelegate).wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true);
+        verifyNoPowerManagerWakeUp();
+
+        setDelegatedMotionWakeUpResult(false);
+
+        // 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();
+        verify(mInputWakeUpDelegate).wakeUpFromMotion(300, SOURCE_ROTARY_ENCODER, false);
+        verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
+    }
+
+    @Test
+    public void testKeyWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
+        setTheaterModeEnabled(false);
+        mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+        mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+        LocalServices.getService(WindowWakeUpPolicyInternal.class)
+                .setInputWakeUpDelegate(mInputWakeUpDelegate);
+
+        setDelegatedKeyWakeUpResult(true);
+
+        // 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();
+        verify(mInputWakeUpDelegate).wakeUpFromKey(200, KEYCODE_POWER, true);
+        verifyNoPowerManagerWakeUp();
+
+        setDelegatedKeyWakeUpResult(false);
+
+        // 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();
+        verify(mInputWakeUpDelegate).wakeUpFromKey(300, KEYCODE_STEM_PRIMARY, false);
+        verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_KEY, "android.policy:KEY");
+    }
+
+    @Test
+    public void testDelegatedKeyWakeIsSubjectToPolicyChecks() {
+        mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+        setDelegatedKeyWakeUpResult(true);
+        setTheaterModeEnabled(true);
+        setBooleanRes(config_allowTheaterModeWakeFromKey, false);
+        setBooleanRes(config_allowTheaterModeWakeFromPowerKey, false);
+        mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+        LocalServices.getService(WindowWakeUpPolicyInternal.class)
+                .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();
+        verify(mInputWakeUpDelegate, never()).wakeUpFromKey(anyLong(), anyInt(), anyBoolean());
+    }
+
+    @Test
+    public void testDelegatedMotionWakeIsSubjectToPolicyChecks() {
+        mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
+        setDelegatedMotionWakeUpResult(true);
+        setTheaterModeEnabled(true);
+        setBooleanRes(config_allowTheaterModeWakeFromMotion, false);
+        mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+        LocalServices.getService(WindowWakeUpPolicyInternal.class)
+                .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();
+        verify(mInputWakeUpDelegate, never()).wakeUpFromMotion(anyLong(), anyInt(), anyBoolean());
+    }
+
+    @Test
+    public void testWakeUpFromMotion() {
+        runPowerManagerUpChecks(
+                () -> mPolicy.wakeUpFromMotion(mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, true),
+                config_allowTheaterModeWakeFromMotion,
+                WAKE_REASON_WAKE_MOTION,
+                "android.policy:MOTION");
+    }
+
+    @Test
+    public void testWakeUpFromKey_nonPowerKey() {
+        runPowerManagerUpChecks(
+                () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_HOME, true),
+                config_allowTheaterModeWakeFromKey,
+                WAKE_REASON_WAKE_KEY,
+                "android.policy:KEY");
+    }
+
+    @Test
+    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
+        // `config_allowTheaterModeWakeFromPowerKey` in the checks.
+        setBooleanRes(config_allowTheaterModeWakeFromKey, false);
+
+        // Test with power key
+        runPowerManagerUpChecks(
+                () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, true),
+                config_allowTheaterModeWakeFromPowerKey,
+                WAKE_REASON_POWER_BUTTON,
+                "android.policy:POWER");
+
+        // Test that power key wake ups happen during theater mode as long as wake-keys are allowed
+        // even if the power-key specific theater mode config is disabled.
+        setBooleanRes(config_allowTheaterModeWakeFromPowerKey, false);
+        runPowerManagerUpChecks(
+                () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, false),
+                config_allowTheaterModeWakeFromKey,
+                WAKE_REASON_POWER_BUTTON,
+                "android.policy:POWER");
+    }
+
+    @Test
+    public void testWakeUpFromLid() {
+        runPowerManagerUpChecks(
+                () -> mPolicy.wakeUpFromLid(),
+                config_allowTheaterModeWakeFromLidSwitch,
+                WAKE_REASON_LID,
+                "android.policy:LID");
+    }
+
+    @Test
+    public void testWakeUpFromWakeGesture() {
+        runPowerManagerUpChecks(
+                () -> mPolicy.wakeUpFromWakeGesture(),
+                config_allowTheaterModeWakeFromGesture,
+                WAKE_REASON_GESTURE,
+                "android.policy:GESTURE");
+    }
+
+    @Test
+    public void testwakeUpFromCameraCover() {
+        runPowerManagerUpChecks(
+                () -> mPolicy.wakeUpFromCameraCover(mClock.uptimeMillis()),
+                config_allowTheaterModeWakeFromCameraLens,
+                WAKE_REASON_CAMERA_LAUNCH,
+                "android.policy:CAMERA_COVER");
+    }
+
+    @Test
+    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
+        // `config_allowTheaterModeWakeFromPowerKey` in the checks.
+        setBooleanRes(config_allowTheaterModeWakeFromKey, false);
+
+        runPowerManagerUpChecks(
+                () -> mPolicy.wakeUpFromPowerKeyCameraGesture(),
+                config_allowTheaterModeWakeFromPowerKey,
+                WAKE_REASON_CAMERA_LAUNCH,
+                "android.policy:CAMERA_GESTURE_PREVENT_LOCK");
+    }
+
+    private void runPowerManagerUpChecks(
+            BooleanSupplier wakeUpCall,
+            int theatherModeWakeResId,
+            int expectedWakeReason,
+            String expectedWakeDetails) {
+        // Test under theater mode enabled.
+        setTheaterModeEnabled(true);
+
+        Mockito.reset(mPowerManager);
+        setBooleanRes(theatherModeWakeResId, true);
+        mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+        setUptimeMillis(200);
+        assertWithMessage("Wake should happen in theater mode when config allows it.")
+                .that(wakeUpCall.getAsBoolean()).isTrue();
+        verify(mPowerManager).wakeUp(200L, expectedWakeReason, expectedWakeDetails);
+
+        Mockito.reset(mPowerManager);
+        setBooleanRes(theatherModeWakeResId, false);
+        mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+        setUptimeMillis(250);
+        assertWithMessage("Wake should not happen in theater mode when config disallows it.")
+                .that(wakeUpCall.getAsBoolean()).isFalse();
+        verifyNoPowerManagerWakeUp();
+
+        // Cases when theater mode is disabled.
+        setTheaterModeEnabled(false);
+
+        Mockito.reset(mPowerManager);
+        setBooleanRes(theatherModeWakeResId, true);
+        mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+        setUptimeMillis(300);
+        assertWithMessage("Wake should happen when not in theater mode.")
+                .that(wakeUpCall.getAsBoolean()).isTrue();
+        verify(mPowerManager).wakeUp(300L, expectedWakeReason, expectedWakeDetails);
+
+        Mockito.reset(mPowerManager);
+        setBooleanRes(theatherModeWakeResId, false);
+        mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+        setUptimeMillis(350);
+        assertWithMessage("Wake should happen when not in theater mode.")
+                .that(wakeUpCall.getAsBoolean()).isTrue();
+        verify(mPowerManager).wakeUp(350L, expectedWakeReason, expectedWakeDetails);
+    }
+
+    private void verifyNoPowerManagerWakeUp() {
+        verify(mPowerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
+    }
+
+    private void setBooleanRes(int resId, boolean val) {
+        when(mResourcesSpy.getBoolean(resId)).thenReturn(val);
+    }
+
+    private void setUptimeMillis(long uptimeMillis) {
+        when(mClock.uptimeMillis()).thenReturn(uptimeMillis);
+    }
+
+    private void setTheaterModeEnabled(boolean enabled) {
+        Settings.Global.putInt(
+                mContextSpy.getContentResolver(), Settings.Global.THEATER_MODE_ON, enabled ? 1 : 0);
+    }
+
+    private void setDelegatedMotionWakeUpResult(boolean result) {
+        when(mInputWakeUpDelegate.wakeUpFromMotion(anyLong(), anyInt(), anyBoolean()))
+                .thenReturn(result);
+    }
+
+    private void setDelegatedKeyWakeUpResult(boolean result) {
+        when(mInputWakeUpDelegate.wakeUpFromKey(anyLong(), anyInt(), anyBoolean()))
+                .thenReturn(result);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
new file mode 100644
index 0000000..71dbc57
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
+
+import org.junit.After;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Build/Install/Run:
+ *  atest WmTests:SensitiveContentPackagesTest
+ */
+@SmallTest
+@Presubmit
+public class SensitiveContentPackagesTest {
+    private static final String APP_PKG_1 = "com.android.server.wm.one";
+    private static final String APP_PKG_2 = "com.android.server.wm.two";
+    private static final String APP_PKG_3 = "com.android.server.wm.three";
+
+    private static final int APP_UID_1 = 5;
+    private static final int APP_UID_2 = 6;
+    private static final int APP_UID_3 = 7;
+
+
+    private final SensitiveContentPackages mSensitiveContentPackages =
+            new SensitiveContentPackages();
+
+    @After
+    public void tearDown() {
+        mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+    }
+
+    @Test
+    public void setShouldBlockScreenCaptureForApp() {
+        Set<PackageInfo> blockedApps =
+                Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
+                        new PackageInfo(APP_PKG_1, APP_UID_2),
+                        new PackageInfo(APP_PKG_2, APP_UID_1),
+                        new PackageInfo(APP_PKG_2, APP_UID_2));
+
+        mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedApps);
+
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+        assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+    }
+
+    @Test
+    public void setShouldBlockScreenCaptureForApp_empty() {
+        Set<PackageInfo> blockedApps =
+                Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
+                        new PackageInfo(APP_PKG_1, APP_UID_2),
+                        new PackageInfo(APP_PKG_2, APP_UID_1),
+                        new PackageInfo(APP_PKG_2, APP_UID_2));
+
+        mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedApps);
+        mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+        assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 45e1e95..b360800 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -43,6 +43,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
+import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_PARENT_TASK;
 import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -1620,6 +1621,29 @@
     }
 
     @Test
+    public void testBoostDimmingTaskFragmentOnTask() {
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final Task task = createTask(mDisplayContent);
+        final TaskFragment primary = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final TaskFragment secondary = createTaskFragmentWithEmbeddedActivity(task, organizer);
+        final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+
+        primary.mVisibleRequested = true;
+        secondary.mVisibleRequested = true;
+        primary.setAdjacentTaskFragment(secondary);
+        secondary.setAdjacentTaskFragment(primary);
+        primary.setEmbeddedDimArea(EMBEDDED_DIM_AREA_PARENT_TASK);
+        doReturn(true).when(primary).shouldBoostDimmer();
+        task.assignChildLayers(t);
+
+        // The layers are initially assigned via the hierarchy, but the primary will be boosted and
+        // assigned again to above of the secondary.
+        verify(primary).assignLayer(t, 0);
+        verify(secondary).assignLayer(t, 1);
+        verify(primary).assignLayer(t, 2);
+    }
+
+    @Test
     public void testMoveOrCreateDecorSurface() {
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final Task task =  new TaskBuilder(mSupervisor).setCreateActivity(true).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 21fee72..a1cc8d5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -80,6 +80,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
 import android.util.MergedConfiguration;
 import android.view.ContentRecordingSession;
 import android.view.IWindow;
@@ -102,16 +103,19 @@
 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
 import com.android.internal.os.IResultReceiver;
 import com.android.server.LocalServices;
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
 import com.android.server.wm.WindowManagerService.WindowContainerInfo;
 
 import com.google.common.truth.Expect;
 
+import org.junit.After;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
 import java.util.ArrayList;
+import java.util.Collections;
 
 /**
  * Build/Install/Run:
@@ -133,6 +137,11 @@
     @Rule
     public Expect mExpect = Expect.create();
 
+    @After
+    public void tearDown() {
+        mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+    }
+
     @Test
     public void testIsRequestedOrientationMapped() {
         mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ true,
@@ -815,6 +824,42 @@
     }
 
     @Test
+    public void setShouldBlockScreenCaptureForApp() {
+        String testPackage = "test";
+        int ownerId1 = 20;
+        int ownerId2 = 21;
+        PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
+        ArraySet<PackageInfo> blockedPackages = new ArraySet();
+        blockedPackages.add(blockedPackage);
+
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        wmInternal.setShouldBlockScreenCaptureForApp(blockedPackages);
+
+        assertTrue(mWm.mSensitiveContentPackages
+                .shouldBlockScreenCaptureForApp(testPackage, ownerId1));
+        assertFalse(mWm.mSensitiveContentPackages
+                .shouldBlockScreenCaptureForApp(testPackage, ownerId2));
+        verify(mWm).refreshScreenCaptureDisabled();
+    }
+
+    @Test
+    public void setShouldBlockScreenCaptureForApp_emptySet_clearsCache() {
+        String testPackage = "test";
+        int ownerId1 = 20;
+        PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
+        ArraySet<PackageInfo> blockedPackages = new ArraySet();
+        blockedPackages.add(blockedPackage);
+
+        WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+        wmInternal.setShouldBlockScreenCaptureForApp(blockedPackages);
+        wmInternal.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+
+        assertFalse(mWm.mSensitiveContentPackages
+                .shouldBlockScreenCaptureForApp(testPackage, ownerId1));
+        verify(mWm, times(2)).refreshScreenCaptureDisabled();
+    }
+
+    @Test
     public void testisLetterboxBackgroundMultiColored() {
         assertThat(setupLetterboxConfigurationWithBackgroundType(
                 LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING)).isTrue();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index f24baba..fb4edfa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -55,6 +55,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.notification.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION;
 import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
 import static com.android.server.wm.WindowContainer.SYNC_STATE_WAITING_FOR_DRAW;
@@ -90,6 +91,7 @@
 import android.os.InputConfig;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.util.ArraySet;
 import android.util.MergedConfiguration;
 import android.view.Gravity;
@@ -109,7 +111,9 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.server.testutils.StubTransaction;
+import com.android.server.wm.SensitiveContentPackages.PackageInfo;
 
+import org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -130,6 +134,11 @@
 @RunWith(WindowTestRunner.class)
 public class WindowStateTests extends WindowTestsBase {
 
+    @After
+    public void tearDown() {
+        mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+    }
+
     @Test
     public void testIsParentWindowHidden() {
         final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow");
@@ -1373,6 +1382,28 @@
         assertThat(listener.mIsVisibleForImeTargetOverlay).isFalse();
     }
 
+    @Test
+    @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION)
+    public void testIsSecureLocked_sensitiveContentProtectionManagerEnabled() {
+        String testPackage = "test";
+        int ownerId1 = 20;
+        int ownerId2 = 21;
+        final WindowState window1 = createWindow(null, TYPE_APPLICATION, "window1", ownerId1);
+        final WindowState window2 = createWindow(null, TYPE_APPLICATION, "window2", ownerId2);
+
+        // Setting packagename for targeted feature
+        window1.mAttrs.packageName = testPackage;
+        window2.mAttrs.packageName = testPackage;
+
+        PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
+        ArraySet<PackageInfo> blockedPackages = new ArraySet();
+        blockedPackages.add(blockedPackage);
+        mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedPackages);
+
+        assertTrue(window1.isSecureLocked());
+        assertFalse(window2.isSecureLocked());
+    }
+
     private static class TestImeTargetChangeListener implements ImeTargetChangeListener {
         private IBinder mImeTargetToken;
         private boolean mIsRemoved;
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index e981e1f..69594f2 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -183,6 +183,9 @@
      * @param cardId   The Id of the eUICC.
      * @param executor The executor through which the callback should be invoked.
      * @param callback The callback to get the result code and all the profiles.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void requestAllProfiles(String cardId, @CallbackExecutor Executor executor,
             ResultCallback<EuiccProfileInfo[]> callback) {
@@ -212,6 +215,9 @@
      * @param iccid    The iccid of the profile.
      * @param executor The executor through which the callback should be invoked.
      * @param callback The callback to get the result code and profile.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void requestProfile(String cardId, String iccid, @CallbackExecutor Executor executor,
             ResultCallback<EuiccProfileInfo> callback) {
@@ -244,6 +250,9 @@
      *                  ICCID is known, an APDU will be sent through to read the enabled profile.
      * @param executor  The executor through which the callback should be invoked.
      * @param callback  The callback to get the result code and the profile.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void requestEnabledProfileForPort(@NonNull String cardId, int portIndex,
             @NonNull @CallbackExecutor Executor executor,
@@ -276,6 +285,9 @@
      * @param refresh  Whether sending the REFRESH command to modem.
      * @param executor The executor through which the callback should be invoked.
      * @param callback The callback to get the result code.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void disableProfile(String cardId, String iccid, boolean refresh,
             @CallbackExecutor Executor executor, ResultCallback<Void> callback) {
@@ -307,6 +319,9 @@
      * @param refresh  Whether sending the REFRESH command to modem.
      * @param executor The executor through which the callback should be invoked.
      * @param callback The callback to get the result code and the EuiccProfileInfo enabled.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      * @deprecated instead use {@link #switchToProfile(String, String, int, boolean, Executor,
      * ResultCallback)}
      */
@@ -344,6 +359,9 @@
      * @param refresh   Whether sending the REFRESH command to modem.
      * @param executor  The executor through which the callback should be invoked.
      * @param callback  The callback to get the result code and the EuiccProfileInfo enabled.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void switchToProfile(@Nullable String cardId, @Nullable String iccid, int portIndex,
             boolean refresh, @NonNull @CallbackExecutor Executor executor,
@@ -375,6 +393,9 @@
      * @param nickname The nickname of the profile.
      * @param executor The executor through which the callback should be invoked.
      * @param callback The callback to get the result code.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void setNickname(String cardId, String iccid, String nickname,
             @CallbackExecutor Executor executor, ResultCallback<Void> callback) {
@@ -404,6 +425,9 @@
      * @param iccid The iccid of the profile.
      * @param executor The executor through which the callback should be invoked.
      * @param callback The callback to get the result code.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void deleteProfile(String cardId, String iccid, @CallbackExecutor Executor executor,
             ResultCallback<Void> callback) {
@@ -434,6 +458,9 @@
      *     EuiccCard for details.
      * @param executor The executor through which the callback should be invoked.
      * @param callback The callback to get the result code.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void resetMemory(String cardId, @ResetOption int options,
             @CallbackExecutor Executor executor, ResultCallback<Void> callback) {
@@ -462,6 +489,9 @@
      * @param cardId The Id of the eUICC.
      * @param executor The executor through which the callback should be invoked.
      * @param callback The callback to get the result code and the default SM-DP+ address.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void requestDefaultSmdpAddress(String cardId, @CallbackExecutor Executor executor,
             ResultCallback<String> callback) {
@@ -490,6 +520,9 @@
      * @param cardId The Id of the eUICC.
      * @param executor The executor through which the callback should be invoked.
      * @param callback The callback to get the result code and the SM-DS address.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void requestSmdsAddress(String cardId, @CallbackExecutor Executor executor,
             ResultCallback<String> callback) {
@@ -519,6 +552,9 @@
      * @param defaultSmdpAddress The default SM-DP+ address to set.
      * @param executor The executor through which the callback should be invoked.
      * @param callback The callback to get the result code.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void setDefaultSmdpAddress(String cardId, String defaultSmdpAddress,
             @CallbackExecutor Executor executor, ResultCallback<Void> callback) {
@@ -548,6 +584,9 @@
      * @param cardId The Id of the eUICC.
      * @param executor The executor through which the callback should be invoked.
      * @param callback the callback to get the result code and the rule authorisation table.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void requestRulesAuthTable(String cardId, @CallbackExecutor Executor executor,
             ResultCallback<EuiccRulesAuthTable> callback) {
@@ -576,6 +615,9 @@
      * @param cardId The Id of the eUICC.
      * @param executor The executor through which the callback should be invoked.
      * @param callback the callback to get the result code and the challenge.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void requestEuiccChallenge(String cardId, @CallbackExecutor Executor executor,
             ResultCallback<byte[]> callback) {
@@ -604,6 +646,9 @@
      * @param cardId The Id of the eUICC.
      * @param executor The executor through which the callback should be invoked.
      * @param callback the callback to get the result code and the info1.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void requestEuiccInfo1(String cardId, @CallbackExecutor Executor executor,
             ResultCallback<byte[]> callback) {
@@ -632,6 +677,9 @@
      * @param cardId The Id of the eUICC.
      * @param executor The executor through which the callback should be invoked.
      * @param callback the callback to get the result code and the info2.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void requestEuiccInfo2(String cardId, @CallbackExecutor Executor executor,
             ResultCallback<byte[]> callback) {
@@ -671,6 +719,9 @@
      * @param executor The executor through which the callback should be invoked.
      * @param callback the callback to get the result code and a byte array which represents a
      *     {@code AuthenticateServerResponse} defined in GSMA RSP v2.0+.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void authenticateServer(String cardId, String matchingId, byte[] serverSigned1,
             byte[] serverSignature1, byte[] euiccCiPkIdToBeUsed, byte[] serverCertificate,
@@ -716,6 +767,9 @@
      * @param executor The executor through which the callback should be invoked.
      * @param callback the callback to get the result code and a byte array which represents a
      *     {@code PrepareDownloadResponse} defined in GSMA RSP v2.0+
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void prepareDownload(String cardId, @Nullable byte[] hashCc, byte[] smdpSigned2,
             byte[] smdpSignature2, byte[] smdpCertificate, @CallbackExecutor Executor executor,
@@ -753,6 +807,9 @@
      * @param executor The executor through which the callback should be invoked.
      * @param callback the callback to get the result code and a byte array which represents a
      *     {@code LoadBoundProfilePackageResponse} defined in GSMA RSP v2.0+.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void loadBoundProfilePackage(String cardId, byte[] boundProfilePackage,
             @CallbackExecutor Executor executor, ResultCallback<byte[]> callback) {
@@ -787,6 +844,9 @@
      * @param executor The executor through which the callback should be invoked.
      * @param callback the callback to get the result code and an byte[] which represents a
      *     {@code CancelSessionResponse} defined in GSMA RSP v2.0+.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void cancelSession(String cardId, byte[] transactionId, @CancelReason int reason,
             @CallbackExecutor Executor executor, ResultCallback<byte[]> callback) {
@@ -820,6 +880,9 @@
      * @param events bits of the event types ({@link EuiccNotification.Event}) to list.
      * @param executor The executor through which the callback should be invoked.
      * @param callback the callback to get the result code and the list of notifications.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void listNotifications(String cardId, @EuiccNotification.Event int events,
             @CallbackExecutor Executor executor, ResultCallback<EuiccNotification[]> callback) {
@@ -850,6 +913,9 @@
      * @param events bits of the event types ({@link EuiccNotification.Event}) to list.
      * @param executor The executor through which the callback should be invoked.
      * @param callback the callback to get the result code and the list of notifications.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void retrieveNotificationList(String cardId, @EuiccNotification.Event int events,
             @CallbackExecutor Executor executor, ResultCallback<EuiccNotification[]> callback) {
@@ -880,6 +946,9 @@
      * @param seqNumber the sequence number of the notification.
      * @param executor The executor through which the callback should be invoked.
      * @param callback the callback to get the result code and the notification.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void retrieveNotification(String cardId, int seqNumber,
             @CallbackExecutor Executor executor, ResultCallback<EuiccNotification> callback) {
@@ -910,6 +979,9 @@
      * @param seqNumber the sequence number of the notification.
      * @param executor The executor through which the callback should be invoked.
      * @param callback the callback to get the result code.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public void removeNotificationFromList(String cardId, int seqNumber,
             @CallbackExecutor Executor executor, ResultCallback<Void> callback) {
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 86fbb04..09d2108 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -927,6 +927,9 @@
      * subscription APIs.
      *
      * @return true if embedded subscriptions are currently enabled.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public boolean isEnabled() {
         // In the future, this may reach out to IEuiccController (if non-null) to check any dynamic
@@ -942,6 +945,9 @@
      * access to the EID of another eUICC.
      *
      * @return the EID. May be null if the eUICC is not ready.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     @Nullable
     public String getEid() {
@@ -963,6 +969,8 @@
      * @return the status of eUICC OTA. If the eUICC is not ready,
      *         {@link OtaStatus#EUICC_OTA_STATUS_UNAVAILABLE} will be returned.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      * @hide
      */
     @SystemApi
@@ -1014,6 +1022,9 @@
      * @param subscription the subscription to download.
      * @param switchAfterDownload if true, the profile will be activated upon successful download.
      * @param callbackIntent a PendingIntent to launch when the operation completes.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
     public void downloadSubscription(DownloadableSubscription subscription,
@@ -1075,6 +1086,9 @@
      * @param resolutionExtras Resolution-specific extras depending on the result of the resolution.
      *     For example, this may indicate whether the user has consented or may include the input
      *     they provided.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      * @hide
      */
     @SystemApi
@@ -1111,6 +1125,9 @@
      *
      * @param subscription the subscription which needs metadata filled in
      * @param callbackIntent a PendingIntent to launch when the operation completes.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      * @hide
      */
     @SystemApi
@@ -1142,6 +1159,9 @@
      * internal system use only.
      *
      * @param callbackIntent a PendingIntent to launch when the operation completes.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      * @hide
      */
     @SystemApi
@@ -1163,6 +1183,9 @@
      * Returns information about the eUICC chip/device.
      *
      * @return the {@link EuiccInfo}. May be null if the eUICC is not ready.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     @Nullable
     public EuiccInfo getEuiccInfo() {
@@ -1188,6 +1211,9 @@
      *
      * @param subscriptionId the ID of the subscription to delete.
      * @param callbackIntent a PendingIntent to launch when the operation completes.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
     public void deleteSubscription(int subscriptionId, PendingIntent callbackIntent) {
@@ -1251,6 +1277,9 @@
      *     {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, or the
      *     calling app must be authorized to manage the active subscription on the target eUICC.
      * @param callbackIntent a PendingIntent to launch when the operation completes.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
     public void switchToSubscription(int subscriptionId, PendingIntent callbackIntent) {
@@ -1312,6 +1341,9 @@
      *     {@link SubscriptionInfo#getPortIndex()}.
      * @param portIndex the index of the port to target for the enabled subscription
      * @param callbackIntent a PendingIntent to launch when the operation completes.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
     public void switchToSubscription(int subscriptionId, int portIndex,
@@ -1349,6 +1381,9 @@
      * @param subscriptionId the ID of the subscription to update.
      * @param nickname the new nickname to apply.
      * @param callbackIntent a PendingIntent to launch when the operation completes.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
     public void updateSubscriptionNickname(
@@ -1376,6 +1411,8 @@
      * @deprecated From R, callers should specify a flag for specific set of subscriptions to erase
      * and use {@link #eraseSubscriptions(int, PendingIntent)} instead
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      * @hide
      */
     @SystemApi
@@ -1402,6 +1439,8 @@
      * @param options flag indicating specific set of subscriptions to erase
      * @param callbackIntent a PendingIntent to launch when the operation completes.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      * @hide
      */
     @SystemApi
@@ -1459,6 +1498,9 @@
      * determine whether a country is supported please check {@link #isSupportedCountry}.
      *
      * @param supportedCountries is a list of strings contains country ISO codes in uppercase.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      * @hide
      */
     @SystemApi
@@ -1487,6 +1529,9 @@
      * determine whether a country is supported please check {@link #isSupportedCountry}.
      *
      * @param unsupportedCountries is a list of strings contains country ISO codes in uppercase.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      * @hide
      */
     @SystemApi
@@ -1512,6 +1557,9 @@
      * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
      *
      * @return list of strings contains country ISO codes in uppercase.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      * @hide
      */
     @SystemApi
@@ -1535,6 +1583,9 @@
      * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
      *
      * @return list of strings contains country ISO codes in uppercase.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      * @hide
      */
     @SystemApi
@@ -1566,6 +1617,9 @@
      * @param countryIso should be the ISO-3166 country code is provided in uppercase 2 character
      * format.
      * @return whether the given country supports eUICC or not.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      * @hide
      */
     @SystemApi
@@ -1630,6 +1684,9 @@
      *
      * @param portIndex is an enumeration of the ports available on the UICC.
      * @return {@code true} if port is available
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public boolean isSimPortAvailable(int portIndex) {
         try {
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index 71bb329..551057f 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -779,6 +779,8 @@
      * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_AVAILABLE_BOOL
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @return true if the user's setting for advanced calling is enabled, false otherwise.
      */
     @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@@ -827,6 +829,8 @@
      * @see #isAdvancedCallingSettingEnabled()
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -865,6 +869,8 @@
      * @param capability The IMS MmTel capability to query.
      * @return {@code true} if the MmTel IMS capability is capable for this subscription, false
      *         otherwise.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -893,6 +899,8 @@
      * @param capability The IMS MmTel capability to query.
      * @return {@code true} if the MmTel IMS capability is available for this subscription, false
      *         otherwise.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide
      */
     @SystemApi
@@ -986,6 +994,8 @@
      *
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @return true if the user’s “Video Calling” setting is currently enabled.
      */
     @RequiresPermission(anyOf = {
@@ -1017,6 +1027,8 @@
      *
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @see #isVtSettingEnabled()
      * @hide
      */
@@ -1060,6 +1072,8 @@
      *
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      */
     @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
     @RequiresPermission(anyOf = {
@@ -1090,6 +1104,8 @@
      *
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @param isEnabled true if the user's setting for Voice over WiFi is enabled, false otherwise=
      * @see #isVoWiFiSettingEnabled()
      * @hide
@@ -1148,6 +1164,8 @@
      *
      * @throws ImsException if the IMS service associated with this subscription is not available or
      * the IMS service is not available.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @return true if the user's setting for Voice over Cross SIM is enabled and false if it is not
      */
     @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
@@ -1192,6 +1210,8 @@
      * </ul>
      * @throws ImsException if the IMS service associated with this subscription is not available or
      * the IMS service is not available.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @param isEnabled true if the user's setting for Voice over Cross SIM is enabled,
      *                 false otherwise
      * @see #isCrossSimCallingEnabled()
@@ -1233,6 +1253,8 @@
      *
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @return true if the user's setting for Voice over WiFi while roaming is enabled, false
      * if disabled.
      */
@@ -1267,6 +1289,8 @@
      *     false otherwise.
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @see #isVoWiFiRoamingSettingEnabled()
      * @hide
      */
@@ -1304,6 +1328,8 @@
      * - {@link #WIFI_MODE_WIFI_PREFERRED}
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @see #setVoWiFiSettingEnabled(boolean)
      * @hide
      */
@@ -1347,6 +1373,8 @@
      *
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @return The Voice over WiFi Mode preference set by the user, which can be one of the
      * following:
      * - {@link #WIFI_MODE_WIFI_ONLY}
@@ -1386,6 +1414,8 @@
      * - {@link #WIFI_MODE_WIFI_PREFERRED}
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @see #getVoWiFiModeSetting()
      * @hide
      */
@@ -1422,6 +1452,8 @@
      *     - {@link #WIFI_MODE_WIFI_PREFERRED}
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @see #setVoWiFiRoamingSettingEnabled(boolean)
      * @hide
      */
@@ -1458,6 +1490,8 @@
      *     - {@link #WIFI_MODE_WIFI_PREFERRED}
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @see #getVoWiFiRoamingModeSetting()
      * @hide
      */
@@ -1492,6 +1526,8 @@
      * settings.
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @param isEnabled if true RTT should be enabled during calls made on this subscription.
      * @hide
      */
@@ -1535,6 +1571,8 @@
      *
      * @throws IllegalArgumentException if the subscription associated with this operation is not
      * active (SIM is not inserted, ESIM inactive) or invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @see android.telephony.CarrierConfigManager#KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL
      */
     @SuppressAutoDoc // No support for device / profile owner or carrier privileges (b/72967236).
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index 2b49bcd..62d4263 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -250,6 +250,8 @@
      * the {@code ImsService} associated with the subscription is not available. This can happen if
      * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
      * reason.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      */
     @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
     public void registerImsRegistrationCallback(
@@ -294,6 +296,8 @@
      * @param c The {@link RegistrationManager.RegistrationCallback} to be removed.
      * @see android.telephony.SubscriptionManager.OnSubscriptionsChangedListener
      * @see #registerImsRegistrationCallback(Executor, RegistrationCallback)
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      */
     @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
     public void unregisterImsRegistrationCallback(
@@ -329,6 +333,8 @@
      * following: {@link RegistrationManager#REGISTRATION_STATE_NOT_REGISTERED},
      * {@link RegistrationManager#REGISTRATION_STATE_REGISTERING}, or
      * {@link RegistrationManager#REGISTRATION_STATE_REGISTERED}.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      */
     @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
     public void getRegistrationState(@NonNull @CallbackExecutor Executor executor,
@@ -378,6 +384,8 @@
      * {@see AccessNetworkConstants#TRANSPORT_TYPE_WWAN},
      * {@see AccessNetworkConstants#TRANSPORT_TYPE_WLAN}, or
      * {@see AccessNetworkConstants#TRANSPORT_TYPE_INVALID}.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      */
     @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
     public void getRegistrationTransportType(@NonNull @CallbackExecutor Executor executor,
@@ -435,6 +443,8 @@
      * {@link ImsRcsManager} is valid, but the ImsService associated with the subscription is not
      * available. This can happen if the ImsService has crashed, for example, or if the subscription
      * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide
      */
     @SystemApi
@@ -479,6 +489,8 @@
      * @see #addOnAvailabilityChangedListener(Executor, OnAvailabilityChangedListener)
      * @throws ImsException if the IMS service is not available when calling this method.
      * See {@link ImsException#getCode()} for more information on the error codes.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide
      */
     @SystemApi
@@ -525,6 +537,8 @@
      * @see android.telephony.CarrierConfigManager.Ims#KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL
      * @throws ImsException if the IMS service is not available when calling this method.
      * See {@link ImsException#getCode()} for more information on the error codes.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide
      */
     @SystemApi
@@ -563,6 +577,8 @@
      * @see #isCapable(int, int)
      * @throws ImsException if the IMS service is not available when calling this method.
      * See {@link ImsException#getCode()} for more information on the error codes.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide
      */
     @SystemApi
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index 1c5d1e9..62b8420 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -1300,8 +1300,10 @@
      * @param executor The executor that the callback methods will be called on.
      * @param callback The callback instance being registered.
      * @throws ImsException if the subscription associated with this callback is
-     * valid, but the {@link ImsService the service crashed, for example. See
+     * valid, but the service crashed, for example. See
      * {@link ImsException#getCode()} for a more detailed reason.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      */
     @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
     public void registerFeatureProvisioningChangedCallback(
@@ -1327,6 +1329,8 @@
      *
      * @param callback The existing {@link FeatureProvisioningCallback} to be removed.
      * @see #registerFeatureProvisioningChangedCallback(Executor, FeatureProvisioningCallback)
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      */
     public void unregisterFeatureProvisioningChangedCallback(
             @NonNull FeatureProvisioningCallback callback) {
@@ -1347,6 +1351,8 @@
      * @return an integer value for the provided key, or
      * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist.
      * @throws IllegalArgumentException if the key provided was invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide
      */
     @SystemApi
@@ -1369,6 +1375,8 @@
      * @return a String value for the provided key, {@code null} if the key doesn't exist, or
      * {@link StringResultError} if there was an error getting the value for the provided key.
      * @throws IllegalArgumentException if the key provided was invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide
      */
     @SystemApi
@@ -1392,6 +1400,8 @@
      * @param key An integer that represents the provisioning key, which is defined by the OEM.
      * @param value a integer value for the provided key.
      * @return the result of setting the configuration value.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide
      *
      * Note: For compatibility purposes, the integer values [0 - 99] used in
@@ -1420,6 +1430,8 @@
      *     should be appropriately namespaced to avoid collision.
      * @param value a String value for the provided key.
      * @return the result of setting the configuration value.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide
      */
     @SystemApi
@@ -1451,6 +1463,9 @@
      *
      * @see CarrierConfigManager.Ims#KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE
      * @param isProvisioned true if the device is provisioned for UT over IMS, false otherwise.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      */
     @WorkerThread
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -1485,6 +1500,9 @@
      * @return true if the device is provisioned for the capability or does not require
      * provisioning, false if the capability does require provisioning and has not been
      * provisioned yet.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      */
     @WorkerThread
     @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
@@ -1509,6 +1527,9 @@
      * @return true if the device is provisioned for the capability or does not require
      * provisioning, false if the capability does require provisioning and has not been
      * provisioned yet.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
+     *
      * @deprecated Use {@link #getRcsProvisioningStatusForCapability(int, int)} instead,
      * as this only retrieves provisioning information for
      * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
@@ -1546,6 +1567,9 @@
      * @return true if the device is provisioned for the capability or does not require
      * provisioning, false if the capability does require provisioning and has not been
      * provisioned yet.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      */
     @WorkerThread
     @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
@@ -1577,6 +1601,9 @@
      * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL
      * @param isProvisioned true if the device is provisioned for the RCS capability specified,
      *                      false otherwise.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
+     *
      * @deprecated Use {@link #setRcsProvisioningStatusForCapability(int, int, boolean)} instead,
      * as this method only sets provisioning information for
      * {@link ImsRegistrationImplBase#REGISTRATION_TECH_LTE}
@@ -1615,6 +1642,9 @@
      * @see CarrierConfigManager.Ims#KEY_RCS_REQUIRES_PROVISIONING_BUNDLE
      * @param isProvisioned true if the device is provisioned for the RCS capability specified,
      *                      false otherwise.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      */
     @WorkerThread
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@@ -1644,6 +1674,9 @@
      * @return true if provisioning is required for the MMTEL capability and IMS
      * registration technology specified, false if it is not required or if the device does not
      * support IMS.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      */
     @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
     public boolean isProvisioningRequiredForCapability(
@@ -1672,6 +1705,9 @@
      * @return true if provisioning is required for the RCS capability and IMS
      * registration technology specified, false if it is not required or if the device does not
      * support IMS.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      */
     @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
     public boolean isRcsProvisioningRequiredForCapability(
@@ -1700,10 +1736,14 @@
      * @param config The XML file to be read. ASCII/UTF8 encoded text if not compressed.
      * @param isCompressed The XML file is compressed in gzip format and must be decompressed
      *         before being read.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
     public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) {
         if (config == null) {
             throw new IllegalArgumentException("Must include a non-null config XML file.");
@@ -1714,7 +1754,6 @@
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         }
-
     }
 
     /**
@@ -1787,10 +1826,14 @@
      * When the IMS/RCS service receives the RCS client configuration, it will detect
      * the change in the configuration, and trigger the auto-configuration as needed.
      * @param rcc RCS client configuration {@link RcsClientConfiguration}
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
     public void setRcsClientConfiguration(
             @NonNull RcsClientConfiguration rcc) throws ImsException {
         try {
@@ -1826,6 +1869,7 @@
     @RequiresPermission(anyOf = {
             Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
             Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION})
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
     public boolean isRcsVolteSingleRegistrationCapable() throws ImsException {
         try {
             return getITelephony().isRcsVolteSingleRegistrationCapable(mSubId);
@@ -1870,12 +1914,15 @@
     * params (See {@link #setRcsClientConfiguration}) and re register the
     * callback.
     * See {@link ImsException#getCode()} for a more detailed reason.
+    * @throws UnsupportedOperationException If the device does not have
+    *          {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
     * @hide
     */
     @SystemApi
     @RequiresPermission(anyOf = {
             Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
             Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION})
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
     public void registerRcsProvisioningCallback(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull RcsProvisioningCallback callback) throws ImsException {
@@ -1908,12 +1955,15 @@
      * @see #registerRcsProvisioningCallback(Executor, RcsProvisioningCallback)
      * @throws IllegalArgumentException if the subscription associated with
      * this callback is invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(anyOf = {
             Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
             Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION})
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
     public void unregisterRcsProvisioningCallback(
             @NonNull RcsProvisioningCallback callback) {
         try {
@@ -1935,10 +1985,14 @@
      * {@link RcsProvisioningCallback#onConfigurationReset}, then
      * {@link RcsProvisioningCallback#onConfigurationChanged} when the new
      * RCS configuration is received and notified by {@link #notifyRcsAutoConfigurationReceived}
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION)
     public void triggerRcsReconfiguration() {
         try {
             getITelephony().triggerRcsReconfiguration(mSubId);
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 3bb9be0..8925a9e 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -21,9 +21,11 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.IBinder;
@@ -49,6 +51,7 @@
  *
  * @see ImsRcsManager#getUceAdapter() for information on creating an instance of this class.
  */
+@RequiresFeature(PackageManager.FEATURE_TELEPHONY_IMS)
 public class RcsUceAdapter {
     private static final String TAG = "RcsUceAdapter";
 
@@ -585,6 +588,8 @@
      * {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
      * available. This can happen if the ImsService has crashed, for example, or if the subscription
      * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide
      */
     @SystemApi
@@ -682,6 +687,8 @@
      * {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
      * available. This can happen if the ImsService has crashed, for example, or if the subscription
      * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide
      */
     @SystemApi
@@ -759,6 +766,8 @@
      * {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
      * available. This can happen if the ImsService has crashed, for example, or if the subscription
      * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide
      */
     @SystemApi
@@ -800,6 +809,8 @@
      * the {@link ImsService} associated with the subscription is not available. This can happen if
      * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
      * reason.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide
      */
     @SystemApi
@@ -845,6 +856,8 @@
      * the {@link ImsService} associated with the subscription is not available. This can happen if
      * the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
      * reason.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide
      */
     @SystemApi
@@ -901,6 +914,8 @@
      * {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
      * available. This can happen if the ImsService has crashed, for example, or if the subscription
      * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      */
     @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
     public boolean isUceSettingEnabled() throws ImsException {
@@ -954,6 +969,8 @@
      * {@link RcsUceAdapter} is valid, but the ImsService associated with the subscription is not
      * available. This can happen if the ImsService has crashed, for example, or if the subscription
      * becomes inactive. See {@link ImsException#getCode()} for more information on the error codes.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS}.
      * @hide
      */
     @SystemApi
diff --git a/telephony/java/android/telephony/ims/SipDelegateManager.java b/telephony/java/android/telephony/ims/SipDelegateManager.java
index 25ebdd0..abf2105 100644
--- a/telephony/java/android/telephony/ims/SipDelegateManager.java
+++ b/telephony/java/android/telephony/ims/SipDelegateManager.java
@@ -525,6 +525,8 @@
      * @param callback The callback instance being registered.
      * @throws ImsException in the case that the callback can not be registered.
      * See {@link ImsException#getCode} for more information on when this is called.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void registerSipDialogStateCallback(@NonNull Executor executor,
@@ -557,6 +559,9 @@
      * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE}
      *
      * @param callback The callback instance to be unregistered.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}.
      */
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void unregisterSipDialogStateCallback(@NonNull SipDialogStateCallback callback)
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 84777c9..9b5ee0c 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3055,6 +3055,29 @@
     boolean setEmergencyCallToSatelliteHandoverType(int handoverType, int delaySeconds);
 
     /**
+     * This API should be used by only CTS tests to forcefully set the country codes.
+     *
+     * @param reset {@code true} mean the overridden country codes should not be used, {@code false}
+     *              otherwise.
+     * @return {@code true} if the country code is set successfully, {@code false} otherwise.
+     */
+    boolean setCountryCodes(in boolean reset, in List<String> currentNetworkCountryCodes,
+            in Map cachedNetworkCountryCodes, in String locationCountryCode,
+            in long locationCountryCodeTimestampNanos);
+
+    /**
+     * This API should be used by only CTS tests to override the overlay configs of satellite
+     * access controller.
+     *
+     * @param reset {@code true} mean the overridden configs should not be used, {@code false}
+     *              otherwise.
+     * @return {@code true} if the overlay configs are set successfully, {@code false} otherwise.
+     */
+    boolean setSatelliteAccessControlOverlayConfigs(in boolean reset, in boolean isAllowed,
+            in String s2CellFile, in long locationFreshDurationNanos,
+            in List<String> satelliteCountryCodes);
+
+    /**
      * Test method to confirm the file contents are not altered.
      */
      @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
diff --git a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
index ba9e4a8..f82d9ca 100644
--- a/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
+++ b/tests/UpdatableSystemFontTest/src/com/android/updatablesystemfont/UpdatableSystemFontTest.java
@@ -130,14 +130,13 @@
     private static final Pattern PATTERN_SYSTEM_FONT_FILES =
             Pattern.compile("^/(system|product)/fonts/");
 
-    private String mKeyId;
     private FontManager mFontManager;
     private UiDevice mUiDevice;
 
     @Before
     public void setUp() throws Exception {
         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-        mKeyId = insertCert(CERT_PATH);
+        insertCert(CERT_PATH);
         mFontManager = context.getSystemService(FontManager.class);
         expectCommandToSucceed("cmd font clear");
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
@@ -147,9 +146,6 @@
     public void tearDown() throws Exception {
         // Ignore errors because this may fail if updatable system font is not enabled.
         runShellCommand("cmd font clear", null);
-        if (mKeyId != null) {
-            expectCommandToSucceed("mini-keyctl unlink " + mKeyId + " .fs-verity");
-        }
     }
 
     @Test
@@ -369,20 +365,11 @@
         assertThat(isFileOpenedBy(fontPath, EMOJI_RENDERING_TEST_APP_ID)).isFalse();
     }
 
-    private static String insertCert(String certPath) throws Exception {
-        Pair<String, String> result;
-        try (InputStream is = new FileInputStream(certPath)) {
-            result = runShellCommand("mini-keyctl padd asymmetric fsv_test .fs-verity", is);
-        }
+    private static void insertCert(String certPath) throws Exception {
         // /data/local/tmp is not readable by system server. Copy a cert file to /data/fonts
         final String copiedCert = "/data/fonts/debug_cert.der";
         runShellCommand("cp " + certPath + " " + copiedCert, null);
         runShellCommand("cmd font install-debug-cert " + copiedCert, null);
-        // Assert that there are no errors.
-        assertThat(result.second).isEmpty();
-        String keyId = result.first.trim();
-        assertThat(keyId).matches("^\\d+$");
-        return keyId;
     }
 
     private int updateFontFile(String fontPath, String signaturePath) throws IOException {