Merge "Add final editing metrics" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 1c6df75..7e6c30f 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -54,6 +54,7 @@
     ":android.service.controls.flags-aconfig-java{.generated_srcjars}",
     ":android.service.dreams.flags-aconfig-java{.generated_srcjars}",
     ":android.service.notification.flags-aconfig-java{.generated_srcjars}",
+    ":android.service.appprediction.flags-aconfig-java{.generated_srcjars}",
     ":android.service.voice.flags-aconfig-java{.generated_srcjars}",
     ":android.speech.flags-aconfig-java{.generated_srcjars}",
     ":android.systemserver.flags-aconfig-java{.generated_srcjars}",
@@ -125,6 +126,7 @@
         "android.provider.flags-aconfig",
         "android.security.flags-aconfig",
         "android.server.app.flags-aconfig",
+        "android.service.appprediction.flags-aconfig",
         "android.service.autofill.flags-aconfig",
         "android.service.chooser.flags-aconfig",
         "android.service.controls.flags-aconfig",
@@ -726,6 +728,19 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// App prediction
+aconfig_declarations {
+    name: "android.service.appprediction.flags-aconfig",
+    package: "android.service.appprediction.flags",
+    srcs: ["core/java/android/service/appprediction/flags/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.service.appprediction.flags-aconfig-java",
+    aconfig_declarations: "android.service.appprediction.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Controls
 aconfig_declarations {
     name: "android.service.controls.flags-aconfig",
@@ -855,6 +870,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "device_policy_aconfig_flags_lib_host",
+    aconfig_declarations: "device_policy_aconfig_flags",
+    host_supported: true,
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 cc_aconfig_library {
     name: "device_policy_aconfig_flags_c_lib",
     aconfig_declarations: "device_policy_aconfig_flags",
diff --git a/OWNERS b/OWNERS
index 935b768..6bab92b 100644
--- a/OWNERS
+++ b/OWNERS
@@ -40,4 +40,6 @@
 
 per-file PERFORMANCE_OWNERS = file:/PERFORMANCE_OWNERS
 
-per-file PACKAGE_MANAGER_OWNERS = file:/PACKAGE_MANAGER_OWNERS
\ No newline at end of file
+per-file PACKAGE_MANAGER_OWNERS = file:/PACKAGE_MANAGER_OWNERS
+
+per-file WEAR_OWNERS = file:/WEAR_OWNERS
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 93febca4..2babf6a 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -96,14 +96,19 @@
 android_ravenwood_libgroup {
     name: "ravenwood-runtime",
     libs: [
-        "framework-minus-apex.ravenwood",
-        "hoststubgen-helper-runtime.ravenwood",
-        "hoststubgen-helper-framework-runtime.ravenwood",
+        // Prefixed with "200" to ensure it's sorted early in Tradefed classpath
+        // so that we provide a concrete implementation before Mainline stubs
+        "200-kxml2-android",
         "all-updatable-modules-system-stubs",
+        "android.test.mock.ravenwood",
+        "framework-minus-apex.ravenwood",
+        "hoststubgen-helper-framework-runtime.ravenwood",
+        "hoststubgen-helper-runtime.ravenwood",
+
+        // Provide runtime versions of utils linked in below
         "junit",
         "truth",
         "ravenwood-junit-impl",
-        "android.test.mock.ravenwood",
         "mockito-ravenwood-prebuilt",
         "inline-mockito-ravenwood-prebuilt",
     ],
diff --git a/WEAR_OWNERS b/WEAR_OWNERS
index 4127f99..da8c83e 100644
--- a/WEAR_OWNERS
+++ b/WEAR_OWNERS
@@ -10,3 +10,4 @@
 rwmyers@google.com
 nalmalki@google.com
 shijianli@google.com
+latkin@google.com
diff --git a/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java b/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java
index 123b2ee..0fd2449 100644
--- a/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java
+++ b/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java
@@ -21,8 +21,11 @@
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.MotionEvent.TOOL_TYPE_FINGER;
 import static android.view.MotionEvent.TOOL_TYPE_STYLUS;
+import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
 
 
+import static org.junit.Assume.assumeFalse;
+
 import android.app.Instrumentation;
 import android.content.Context;
 import android.perftests.utils.BenchmarkState;
@@ -186,6 +189,7 @@
 
     @Test
     public void onInputConnectionCreated() {
+        assumeFalse(initiationWithoutInputConnection());
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         final View view = new View(mContext);
         final EditorInfo editorInfo = new EditorInfo();
@@ -199,6 +203,7 @@
 
     @Test
     public void onInputConnectionClosed() {
+        assumeFalse(initiationWithoutInputConnection());
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         final View view = new View(mContext);
         while (state.keepRunning()) {
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 52c0ac1..7de6799 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -124,15 +124,6 @@
     @Overridable // Aid in testing
     public static final long ENFORCE_MINIMUM_TIME_WINDOWS = 311402873L;
 
-    /**
-     * Require that minimum latencies and override deadlines are nonnegative.
-     *
-     * @hide
-     */
-    @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-    public static final long REJECT_NEGATIVE_DELAYS_AND_DEADLINES = 323349338L;
-
     /** @hide */
     @IntDef(prefix = { "NETWORK_TYPE_" }, value = {
             NETWORK_TYPE_NONE,
@@ -701,14 +692,14 @@
      * @see JobInfo.Builder#setMinimumLatency(long)
      */
     public long getMinLatencyMillis() {
-        return Math.max(0, minLatencyMillis);
+        return minLatencyMillis;
     }
 
     /**
      * @see JobInfo.Builder#setOverrideDeadline(long)
      */
     public long getMaxExecutionDelayMillis() {
-        return Math.max(0, maxExecutionDelayMillis);
+        return maxExecutionDelayMillis;
     }
 
     /**
@@ -827,7 +818,7 @@
      * @hide
      */
     public boolean hasEarlyConstraint() {
-        return hasEarlyConstraint && minLatencyMillis > 0;
+        return hasEarlyConstraint;
     }
 
     /**
@@ -836,7 +827,7 @@
      * @hide
      */
     public boolean hasLateConstraint() {
-        return hasLateConstraint && maxExecutionDelayMillis >= 0;
+        return hasLateConstraint;
     }
 
     @Override
@@ -1878,13 +1869,6 @@
          * Because it doesn't make sense setting this property on a periodic job, doing so will
          * throw an {@link java.lang.IllegalArgumentException} when
          * {@link android.app.job.JobInfo.Builder#build()} is called.
-         *
-         * Negative latencies also don't make sense for a job and are indicative of an error,
-         * so starting in Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
-         * setting a negative deadline will result in
-         * {@link android.app.job.JobInfo.Builder#build()} throwing an
-         * {@link java.lang.IllegalArgumentException}.
-         *
          * @param minLatencyMillis Milliseconds before which this job will not be considered for
          *                         execution.
          * @see JobInfo#getMinLatencyMillis()
@@ -1908,13 +1892,6 @@
          * throw an {@link java.lang.IllegalArgumentException} when
          * {@link android.app.job.JobInfo.Builder#build()} is called.
          *
-         * <p>
-         * Negative deadlines also don't make sense for a job and are indicative of an error,
-         * so starting in Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM},
-         * setting a negative deadline will result in
-         * {@link android.app.job.JobInfo.Builder#build()} throwing an
-         * {@link java.lang.IllegalArgumentException}.
-         *
          * <p class="note">
          * Since a job will run once the deadline has passed regardless of the status of other
          * constraints, setting a deadline of 0 (or a {@link #setMinimumLatency(long) delay} equal
@@ -2212,15 +2189,13 @@
         public JobInfo build() {
             return build(Compatibility.isChangeEnabled(DISALLOW_DEADLINES_FOR_PREFETCH_JOBS),
                     Compatibility.isChangeEnabled(REJECT_NEGATIVE_NETWORK_ESTIMATES),
-                    Compatibility.isChangeEnabled(ENFORCE_MINIMUM_TIME_WINDOWS),
-                    Compatibility.isChangeEnabled(REJECT_NEGATIVE_DELAYS_AND_DEADLINES));
+                    Compatibility.isChangeEnabled(ENFORCE_MINIMUM_TIME_WINDOWS));
         }
 
         /** @hide */
         public JobInfo build(boolean disallowPrefetchDeadlines,
                 boolean rejectNegativeNetworkEstimates,
-                boolean enforceMinimumTimeWindows,
-                boolean rejectNegativeDelaysAndDeadlines) {
+                boolean enforceMinimumTimeWindows) {
             // This check doesn't need to be inside enforceValidity. It's an unnecessary legacy
             // check that would ideally be phased out instead.
             if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
@@ -2230,7 +2205,7 @@
             }
             JobInfo jobInfo = new JobInfo(this);
             jobInfo.enforceValidity(disallowPrefetchDeadlines, rejectNegativeNetworkEstimates,
-                    enforceMinimumTimeWindows, rejectNegativeDelaysAndDeadlines);
+                    enforceMinimumTimeWindows);
             return jobInfo;
         }
 
@@ -2250,8 +2225,7 @@
      */
     public final void enforceValidity(boolean disallowPrefetchDeadlines,
             boolean rejectNegativeNetworkEstimates,
-            boolean enforceMinimumTimeWindows,
-            boolean rejectNegativeDelaysAndDeadlines) {
+            boolean enforceMinimumTimeWindows) {
         // Check that network estimates require network type and are reasonable values.
         if ((networkDownloadBytes > 0 || networkUploadBytes > 0 || minimumNetworkChunkBytes > 0)
                 && networkRequest == null) {
@@ -2285,17 +2259,6 @@
             throw new IllegalArgumentException("Minimum chunk size must be positive");
         }
 
-        if (rejectNegativeDelaysAndDeadlines) {
-            if (minLatencyMillis < 0) {
-                throw new IllegalArgumentException(
-                        "Minimum latency is negative: " + minLatencyMillis);
-            }
-            if (maxExecutionDelayMillis < 0) {
-                throw new IllegalArgumentException(
-                        "Override deadline is negative: " + maxExecutionDelayMillis);
-            }
-        }
-
         final boolean hasDeadline = maxExecutionDelayMillis != 0L;
         // Check that a deadline was not set on a periodic job.
         if (isPeriodic) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index f819f15..a83c099 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -4850,7 +4850,7 @@
                     Slog.w(TAG, "Uid " + uid + " set bias on its job");
                     return new JobInfo.Builder(job)
                             .setBias(JobInfo.BIAS_DEFAULT)
-                            .build(false, false, false, false);
+                            .build(false, false, false);
                 }
             }
 
@@ -4874,9 +4874,7 @@
                             JobInfo.DISALLOW_DEADLINES_FOR_PREFETCH_JOBS, callingUid),
                     rejectNegativeNetworkEstimates,
                     CompatChanges.isChangeEnabled(
-                            JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS, callingUid),
-                    CompatChanges.isChangeEnabled(
-                            JobInfo.REJECT_NEGATIVE_DELAYS_AND_DEADLINES, callingUid));
+                            JobInfo.ENFORCE_MINIMUM_TIME_WINDOWS, callingUid));
             if ((job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
                 getContext().enforceCallingOrSelfPermission(
                         android.Manifest.permission.CONNECTIVITY_INTERNAL, TAG);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index d8934d8..53b14d6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -1495,7 +1495,7 @@
                 // return value), the deadline is dropped. Periodic jobs require all constraints
                 // to be met, so there's no issue with their deadlines.
                 // The same logic applies for other target SDK-based validation checks.
-                builtJob = jobBuilder.build(false, false, false, false);
+                builtJob = jobBuilder.build(false, false, false);
             } catch (Exception e) {
                 Slog.w(TAG, "Unable to build job from XML, ignoring: " + jobBuilder.summarize(), e);
                 return null;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index edd86e3..a0b9c5f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -652,7 +652,7 @@
                     .build());
             // Don't perform validation checks at this point since we've already passed the
             // initial validation check.
-            job = builder.build(false, false, false, false);
+            job = builder.build(false, false, false);
         }
 
         this.job = job;
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md
index f177586..b6e4e0d 100644
--- a/cmds/uinput/README.md
+++ b/cmds/uinput/README.md
@@ -154,8 +154,7 @@
 
 #### `delay`
 
-Add a delay between the processing of commands. The delay will be timed from when the last delay
-ended, rather than from the current time, to allow for more precise timings to be produced.
+Add a delay to command processing
 
 | Field         | Type          | Description                |
 |:-------------:|:-------------:|:-------------------------- |
diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
index bd61000..a78a465 100644
--- a/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
+++ b/cmds/uinput/jni/com_android_commands_uinput_Device.cpp
@@ -166,14 +166,14 @@
     ::ioctl(mFd, UI_DEV_DESTROY);
 }
 
-void UinputDevice::injectEvent(std::chrono::microseconds timestamp, uint16_t type, uint16_t code,
-                               int32_t value) {
+void UinputDevice::injectEvent(uint16_t type, uint16_t code, int32_t value) {
     struct input_event event = {};
     event.type = type;
     event.code = code;
     event.value = value;
-    event.time.tv_sec = timestamp.count() / 1'000'000;
-    event.time.tv_usec = timestamp.count() % 1'000'000;
+    timespec ts;
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+    TIMESPEC_TO_TIMEVAL(&event.time, &ts);
 
     if (::write(mFd, &event, sizeof(input_event)) < 0) {
         ALOGE("Could not write event %" PRIu16 " %" PRIu16 " with value %" PRId32 " : %s", type,
@@ -268,12 +268,12 @@
     }
 }
 
-static void injectEvent(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jlong timestampMicros,
-                        jint type, jint code, jint value) {
+static void injectEvent(JNIEnv* /* env */, jclass /* clazz */, jlong ptr, jint type, jint code,
+                        jint value) {
     uinput::UinputDevice* d = reinterpret_cast<uinput::UinputDevice*>(ptr);
     if (d != nullptr) {
-        d->injectEvent(std::chrono::microseconds(timestampMicros), static_cast<uint16_t>(type),
-                       static_cast<uint16_t>(code), static_cast<int32_t>(value));
+        d->injectEvent(static_cast<uint16_t>(type), static_cast<uint16_t>(code),
+                       static_cast<int32_t>(value));
     } else {
         ALOGE("Could not inject event, Device* is null!");
     }
@@ -330,7 +330,7 @@
          "(Ljava/lang/String;IIIIIILjava/lang/String;"
          "Lcom/android/commands/uinput/Device$DeviceCallback;)J",
          reinterpret_cast<void*>(openUinputDevice)},
-        {"nativeInjectEvent", "(JJIII)V", reinterpret_cast<void*>(injectEvent)},
+        {"nativeInjectEvent", "(JIII)V", reinterpret_cast<void*>(injectEvent)},
         {"nativeConfigure", "(II[I)V", reinterpret_cast<void*>(configure)},
         {"nativeSetAbsInfo", "(IILandroid/os/Parcel;)V", reinterpret_cast<void*>(setAbsInfo)},
         {"nativeCloseUinputDevice", "(J)V", reinterpret_cast<void*>(closeUinputDevice)},
diff --git a/cmds/uinput/jni/com_android_commands_uinput_Device.h b/cmds/uinput/jni/com_android_commands_uinput_Device.h
index 72c8647..9769a75 100644
--- a/cmds/uinput/jni/com_android_commands_uinput_Device.h
+++ b/cmds/uinput/jni/com_android_commands_uinput_Device.h
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-#include <android-base/unique_fd.h>
-#include <jni.h>
-#include <linux/input.h>
-
-#include <chrono>
 #include <memory>
 #include <vector>
 
+#include <jni.h>
+#include <linux/input.h>
+
+#include <android-base/unique_fd.h>
 #include "src/com/android/commands/uinput/InputAbsInfo.h"
 
 namespace android {
@@ -54,8 +53,7 @@
 
     virtual ~UinputDevice();
 
-    void injectEvent(std::chrono::microseconds timestamp, uint16_t type, uint16_t code,
-                     int32_t value);
+    void injectEvent(uint16_t type, uint16_t code, int32_t value);
     int handleEvents(int events);
 
 private:
diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java
index 76ab475..25d3a34 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Device.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Device.java
@@ -55,7 +55,7 @@
     private final SparseArray<InputAbsInfo> mAbsInfo;
     private final OutputStream mOutputStream;
     private final Object mCond = new Object();
-    private long mTimeToSendNanos;
+    private long mTimeToSend;
 
     static {
         System.loadLibrary("uinputcommand_jni");
@@ -65,8 +65,7 @@
             int productId, int versionId, int bus, int ffEffectsMax, String port,
             DeviceCallback callback);
     private static native void nativeCloseUinputDevice(long ptr);
-    private static native void nativeInjectEvent(long ptr, long timestampMicros, int type, int code,
-                                                 int value);
+    private static native void nativeInjectEvent(long ptr, int type, int code, int value);
     private static native void nativeConfigure(int handle, int code, int[] configs);
     private static native void nativeSetAbsInfo(int handle, int axisCode, Parcel axisParcel);
     private static native int nativeGetEvdevEventTypeByLabel(String label);
@@ -102,54 +101,27 @@
         }
 
         mHandler.obtainMessage(MSG_OPEN_UINPUT_DEVICE, args).sendToTarget();
-        mTimeToSendNanos = SystemClock.uptimeNanos();
-    }
-
-    private long getTimeToSendMillis() {
-        // Since we can only specify delays in milliseconds but evemu timestamps are in
-        // microseconds, we have to round up the delays to avoid setting event timestamps
-        // which are in the future (which the kernel would silently reject and replace with
-        // the current time).
-        //
-        // This should be the same as (long) Math.ceil(mTimeToSendNanos / 1_000_000.0), except
-        // without the precision loss that comes from converting from long to double and back.
-        return mTimeToSendNanos / 1_000_000 + ((mTimeToSendNanos % 1_000_000 > 0) ? 1 : 0);
+        mTimeToSend = SystemClock.uptimeMillis();
     }
 
     /**
      * Inject uinput events to device
      *
      * @param events  Array of raw uinput events.
-     * @param offsetMicros The difference in microseconds between the timestamps of the previous
-     *                     batch of events injected and this batch. If set to -1, the current
-     *                     timestamp will be used.
      */
-    public void injectEvent(int[] events, long offsetMicros) {
+    public void injectEvent(int[] events) {
         // if two messages are sent at identical time, they will be processed in order received
-        SomeArgs args = SomeArgs.obtain();
-        args.arg1 = events;
-        args.argl1 = offsetMicros;
-        args.argl2 = mTimeToSendNanos;
-        Message msg = mHandler.obtainMessage(MSG_INJECT_EVENT, args);
-        mHandler.sendMessageAtTime(msg, getTimeToSendMillis());
+        Message msg = mHandler.obtainMessage(MSG_INJECT_EVENT, events);
+        mHandler.sendMessageAtTime(msg, mTimeToSend);
     }
 
     /**
-     * Delay subsequent device activity by the specified amount of time.
+     * Impose a delay to the device for execution.
      *
-     * <p>Note that although the delay is specified in nanoseconds, due to limitations of {@link
-     * Handler}'s API, scheduling only occurs with millisecond precision. When scheduling an
-     * injection or sync, the time at which it is scheduled will be rounded up to the nearest
-     * millisecond. While this means that a particular injection cannot be scheduled precisely,
-     * rounding errors will not accumulate over time. For example, if five injections are scheduled
-     * with a delay of 1,200,000ns before each one, the total delay will be 6ms, as opposed to the
-     * 10ms it would have been if each individual delay had been rounded up (as {@link EvemuParser}
-     * would otherwise have to do to avoid sending timestamps that are in the future).
-     *
-     * @param delayNanos  Time to delay in unit of nanoseconds.
+     * @param delay  Time to delay in unit of milliseconds.
      */
-    public void addDelayNanos(long delayNanos) {
-        mTimeToSendNanos += delayNanos;
+    public void addDelay(int delay) {
+        mTimeToSend = Math.max(SystemClock.uptimeMillis(), mTimeToSend) + delay;
     }
 
     /**
@@ -159,8 +131,7 @@
      * @param syncToken  The token for this sync command.
      */
     public void syncEvent(String syncToken) {
-        mHandler.sendMessageAtTime(
-                mHandler.obtainMessage(MSG_SYNC_EVENT, syncToken), getTimeToSendMillis());
+        mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_SYNC_EVENT, syncToken), mTimeToSend);
     }
 
     /**
@@ -169,8 +140,7 @@
      */
     public void close() {
         Message msg = mHandler.obtainMessage(MSG_CLOSE_UINPUT_DEVICE);
-        mHandler.sendMessageAtTime(
-                msg, Math.max(SystemClock.uptimeMillis(), getTimeToSendMillis()) + 1);
+        mHandler.sendMessageAtTime(msg, Math.max(SystemClock.uptimeMillis(), mTimeToSend) + 1);
         try {
             synchronized (mCond) {
                 mCond.wait();
@@ -181,7 +151,6 @@
 
     private class DeviceHandler extends Handler {
         private long mPtr;
-        private long mLastInjectTimestampMicros = -1;
         private int mBarrierToken;
 
         DeviceHandler(Looper looper) {
@@ -191,7 +160,7 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_OPEN_UINPUT_DEVICE: {
+                case MSG_OPEN_UINPUT_DEVICE:
                     SomeArgs args = (SomeArgs) msg.obj;
                     String name = (String) args.arg1;
                     mPtr = nativeOpenUinputDevice(name, args.argi1 /* id */,
@@ -208,44 +177,15 @@
                     }
                     args.recycle();
                     break;
-                }
-                case MSG_INJECT_EVENT: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    if (mPtr == 0) {
-                        args.recycle();
-                        break;
+                case MSG_INJECT_EVENT:
+                    if (mPtr != 0) {
+                        int[] events = (int[]) msg.obj;
+                        for (int pos = 0; pos + 2 < events.length; pos += 3) {
+                            nativeInjectEvent(mPtr, events[pos], events[pos + 1], events[pos + 2]);
+                        }
                     }
-                    long offsetMicros = args.argl1;
-                    if (mLastInjectTimestampMicros == -1 || offsetMicros == -1) {
-                        // There's often a delay of a few milliseconds between the time specified to
-                        // Handler.sendMessageAtTime and the handler actually being called, due to
-                        // the way threads are scheduled. We don't take this into account when
-                        // calling addDelayNanos between the first batch of event injections (when
-                        // we set the "base timestamp" from which all others will be offset) and the
-                        // second batch, meaning that the actual time between the handler calls for
-                        // those batches may be less than the offset between their timestamps. When
-                        // that happens, we would pass a timestamp for the second batch that's
-                        // actually in the future. The kernel's uinput API rejects timestamps that
-                        // are in the future and uses the current time instead, making the reported
-                        // timestamps inconsistent with the recording we're replaying.
-                        //
-                        // To prevent this, we need to use the time we scheduled this first batch
-                        // for (in microseconds, to avoid potential rounding up from
-                        // getTimeToSendMillis), rather than the actual current time.
-                        mLastInjectTimestampMicros = args.argl2 / 1000;
-                    } else {
-                        mLastInjectTimestampMicros += offsetMicros;
-                    }
-
-                    int[] events = (int[]) args.arg1;
-                    for (int pos = 0; pos + 2 < events.length; pos += 3) {
-                        nativeInjectEvent(mPtr, mLastInjectTimestampMicros, events[pos],
-                                events[pos + 1], events[pos + 2]);
-                    }
-                    args.recycle();
                     break;
-                }
-                case MSG_CLOSE_UINPUT_DEVICE: {
+                case MSG_CLOSE_UINPUT_DEVICE:
                     if (mPtr != 0) {
                         nativeCloseUinputDevice(mPtr);
                         getLooper().quitSafely();
@@ -258,14 +198,11 @@
                         mCond.notify();
                     }
                     break;
-                }
-                case MSG_SYNC_EVENT: {
+                case MSG_SYNC_EVENT:
                     handleSyncEvent((String) msg.obj);
                     break;
-                }
-                default: {
+                default:
                     throw new IllegalArgumentException("Unknown device message");
-                }
             }
         }
 
diff --git a/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java
index da99162..7652f24 100644
--- a/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java
+++ b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java
@@ -44,7 +44,7 @@
      * recordings, this will always be the same.
      */
     private static final int DEVICE_ID = 1;
-    private static final int REGISTRATION_DELAY_NANOS = 500_000_000;
+    private static final int REGISTRATION_DELAY_MILLIS = 500;
 
     private static class CommentAwareReader {
         private final LineNumberReader mReader;
@@ -152,7 +152,7 @@
         final Event.Builder delayEb = new Event.Builder();
         delayEb.setId(DEVICE_ID);
         delayEb.setCommand(Event.Command.DELAY);
-        delayEb.setDurationNanos(REGISTRATION_DELAY_NANOS);
+        delayEb.setDurationMillis(REGISTRATION_DELAY_MILLIS);
         mQueuedEvents.add(delayEb.build());
     }
 
@@ -175,6 +175,7 @@
             throw new ParsingException(
                     "Invalid timestamp '" + parts[0] + "' (should contain a single '.')", mReader);
         }
+        // TODO(b/310958309): use timeMicros to set the timestamp on the event being sent.
         final long timeMicros =
                 parseLong(timeParts[0], 10) * 1_000_000 + parseInt(timeParts[1], 10);
         final Event.Builder eb = new Event.Builder();
@@ -191,18 +192,21 @@
             return eb.build();
         } else {
             final long delayMicros = timeMicros - mLastEventTimeMicros;
-            eb.setTimestampOffsetMicros(delayMicros);
-            if (delayMicros == 0) {
+            // The shortest delay supported by Handler.sendMessageAtTime (used for timings by the
+            // Device class) is 1ms, so ignore time differences smaller than that.
+            if (delayMicros < 1000) {
+                mLastEventTimeMicros = timeMicros;
                 return eb.build();
+            } else {
+                // Send a delay now, and queue the actual event for the next call.
+                mQueuedEvents.add(eb.build());
+                mLastEventTimeMicros = timeMicros;
+                final Event.Builder delayEb = new Event.Builder();
+                delayEb.setId(DEVICE_ID);
+                delayEb.setCommand(Event.Command.DELAY);
+                delayEb.setDurationMillis((int) (delayMicros / 1000));
+                return delayEb.build();
             }
-            // Send a delay now, and queue the actual event for the next call.
-            mQueuedEvents.add(eb.build());
-            mLastEventTimeMicros = timeMicros;
-            final Event.Builder delayEb = new Event.Builder();
-            delayEb.setId(DEVICE_ID);
-            delayEb.setCommand(Event.Command.DELAY);
-            delayEb.setDurationNanos(delayMicros * 1000);
-            return delayEb.build();
         }
     }
 
diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java
index 9e7ee09..0f16a27 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Event.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Event.java
@@ -99,9 +99,8 @@
     private int mVersionId;
     private int mBusId;
     private int[] mInjections;
-    private long mTimestampOffsetMicros = -1;
     private SparseArray<int[]> mConfiguration;
-    private long mDurationNanos;
+    private int mDurationMillis;
     private int mFfEffectsMax = 0;
     private String mInputPort;
     private SparseArray<InputAbsInfo> mAbsInfo;
@@ -140,28 +139,19 @@
     }
 
     /**
-     * Returns the number of microseconds that should be added to the previous {@code INJECT}
-     * event's timestamp to produce the timestamp for this {@code INJECT} event. A value of -1
-     * indicates that the current timestamp should be used instead.
-     */
-    public long getTimestampOffsetMicros() {
-        return mTimestampOffsetMicros;
-    }
-
-    /**
      * Returns a {@link SparseArray} describing the event codes that should be registered for the
      * device. The keys are uinput ioctl codes (such as those returned from {@link
      * UinputControlCode#getValue()}, while the values are arrays of event codes to be enabled with
      * those ioctls. For example, key 101 (corresponding to {@link UinputControlCode#UI_SET_KEYBIT})
-     * could have values 0x110 ({@code BTN_LEFT}), 0x111 ({@code BTN_RIGHT}), and 0x112
+     * could have values 0x110 ({@code BTN_LEFT}, 0x111 ({@code BTN_RIGHT}), and 0x112
      * ({@code BTN_MIDDLE}).
      */
     public SparseArray<int[]> getConfiguration() {
         return mConfiguration;
     }
 
-    public long getDurationNanos() {
-        return mDurationNanos;
+    public int getDurationMillis() {
+        return mDurationMillis;
     }
 
     public int getFfEffectsMax() {
@@ -192,7 +182,7 @@
             + ", busId=" + mBusId
             + ", events=" + Arrays.toString(mInjections)
             + ", configuration=" + mConfiguration
-            + ", duration=" + mDurationNanos + "ns"
+            + ", duration=" + mDurationMillis + "ms"
             + ", ff_effects_max=" + mFfEffectsMax
             + ", port=" + mInputPort
             + "}";
@@ -221,10 +211,6 @@
             mEvent.mInjections = events;
         }
 
-        public void setTimestampOffsetMicros(long offsetMicros) {
-            mEvent.mTimestampOffsetMicros = offsetMicros;
-        }
-
         /**
          * Sets the event codes that should be registered with a {@code register} command.
          *
@@ -251,8 +237,8 @@
             mEvent.mBusId = busId;
         }
 
-        public void setDurationNanos(long durationNanos) {
-            mEvent.mDurationNanos = durationNanos;
+        public void setDurationMillis(int durationMillis) {
+            mEvent.mDurationMillis = durationMillis;
         }
 
         public void setFfEffectsMax(int ffEffectsMax) {
@@ -285,7 +271,7 @@
                     }
                 }
                 case DELAY -> {
-                    if (mEvent.mDurationNanos <= 0) {
+                    if (mEvent.mDurationMillis <= 0) {
                         throw new IllegalStateException("Delay has missing or invalid duration");
                     }
                 }
diff --git a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
index 6994f0c..ed3ff33 100644
--- a/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
+++ b/cmds/uinput/src/com/android/commands/uinput/JsonStyleParser.java
@@ -71,8 +71,7 @@
                         case "configuration" -> eb.setConfiguration(readConfiguration());
                         case "ff_effects_max" -> eb.setFfEffectsMax(readInt());
                         case "abs_info" -> eb.setAbsInfo(readAbsInfoArray());
-                        // Duration is specified in milliseconds in the JSON-style format.
-                        case "duration" -> eb.setDurationNanos(readInt() * 1_000_000L);
+                        case "duration" -> eb.setDurationMillis(readInt());
                         case "port" -> eb.setInputPort(mReader.nextString());
                         case "syncToken" -> eb.setSyncToken(mReader.nextString());
                         default -> mReader.skipValue();
diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
index 760e981..04df279 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Uinput.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
@@ -134,8 +134,8 @@
         switch (Objects.requireNonNull(e.getCommand())) {
             case REGISTER ->
                     error("Device id=" + e.getId() + " is already registered. Ignoring event.");
-            case INJECT -> d.injectEvent(e.getInjections(), e.getTimestampOffsetMicros());
-            case DELAY -> d.addDelayNanos(e.getDurationNanos());
+            case INJECT -> d.injectEvent(e.getInjections());
+            case DELAY -> d.addDelay(e.getDurationMillis());
             case SYNC -> d.syncEvent(e.getSyncToken());
         }
     }
diff --git a/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java b/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java
index 4dc4b68..a05cc67 100644
--- a/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java
+++ b/cmds/uinput/tests/src/com/android/commands/uinput/tests/EvemuParserTest.java
@@ -183,22 +183,16 @@
     }
 
     private void assertInjectEvent(Event event, int eventType, int eventCode, int value) {
-        assertInjectEvent(event, eventType, eventCode, value, 0);
-    }
-
-    private void assertInjectEvent(Event event, int eventType, int eventCode, int value,
-                                   long timestampOffsetMicros) {
         assertThat(event).isNotNull();
         assertThat(event.getCommand()).isEqualTo(Event.Command.INJECT);
         assertThat(event.getInjections()).asList()
                 .containsExactly(eventType, eventCode, value).inOrder();
-        assertThat(event.getTimestampOffsetMicros()).isEqualTo(timestampOffsetMicros);
     }
 
-    private void assertDelayEvent(Event event, int durationNanos) {
+    private void assertDelayEvent(Event event, int durationMillis) {
         assertThat(event).isNotNull();
         assertThat(event.getCommand()).isEqualTo(Event.Command.DELAY);
-        assertThat(event.getDurationNanos()).isEqualTo(durationNanos);
+        assertThat(event.getDurationMillis()).isEqualTo(durationMillis);
     }
 
     @Test
@@ -213,7 +207,7 @@
         EvemuParser parser = new EvemuParser(reader);
         assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.REGISTER);
         assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY);
-        assertInjectEvent(parser.getNextEvent(), 0x2, 0x0, 1, -1);
+        assertInjectEvent(parser.getNextEvent(), 0x2, 0x0, 1);
         assertInjectEvent(parser.getNextEvent(), 0x2, 0x1, -2);
         assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
     }
@@ -234,17 +228,17 @@
         assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.REGISTER);
         assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY);
 
-        assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1, -1);
+        assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1);
         assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
 
-        assertDelayEvent(parser.getNextEvent(), 10_000_000);
+        assertDelayEvent(parser.getNextEvent(), 10);
 
-        assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 0, 10_000);
+        assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 0);
         assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
 
-        assertDelayEvent(parser.getNextEvent(), 1_000_000_000);
+        assertDelayEvent(parser.getNextEvent(), 1000);
 
-        assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1, 1_000_000);
+        assertInjectEvent(parser.getNextEvent(), 0x1, 0x15, 1);
         assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
     }
 
@@ -455,7 +449,7 @@
         assertThat(regEvent.getBus()).isEqualTo(0x001d);
         assertThat(regEvent.getVendorId()).isEqualTo(0x6cb);
         assertThat(regEvent.getProductId()).isEqualTo(0x0000);
-        // TODO(b/302297266): check version ID once it's supported
+        assertThat(regEvent.getVersionId()).isEqualTo(0x0000);
 
         assertThat(regEvent.getConfiguration().get(UinputControlCode.UI_SET_PROPBIT.getValue()))
                 .asList().containsExactly(0, 2);
@@ -483,7 +477,7 @@
 
         assertThat(parser.getNextEvent().getCommand()).isEqualTo(Event.Command.DELAY);
 
-        assertInjectEvent(parser.getNextEvent(), 0x3, 0x39, 0, -1);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x39, 0);
         assertInjectEvent(parser.getNextEvent(), 0x3, 0x35, 891);
         assertInjectEvent(parser.getNextEvent(), 0x3, 0x36, 333);
         assertInjectEvent(parser.getNextEvent(), 0x3, 0x3a, 56);
@@ -496,8 +490,8 @@
         assertInjectEvent(parser.getNextEvent(), 0x3, 0x18, 56);
         assertInjectEvent(parser.getNextEvent(), 0x0, 0x0, 0);
 
-        assertDelayEvent(parser.getNextEvent(), 6_080_000);
+        assertDelayEvent(parser.getNextEvent(), 6);
 
-        assertInjectEvent(parser.getNextEvent(), 0x3, 0x0035, 888, 6_080);
+        assertInjectEvent(parser.getNextEvent(), 0x3, 0x0035, 888);
     }
 }
diff --git a/core/api/current.txt b/core/api/current.txt
index 8fb7fa2..ec8bc96 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -653,6 +653,7 @@
     field public static final int contentInsetRight = 16843862; // 0x1010456
     field public static final int contentInsetStart = 16843859; // 0x1010453
     field public static final int contentInsetStartWithNavigation = 16844066; // 0x1010522
+    field @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public static final int contentSensitivity;
     field public static final int contextClickable = 16844007; // 0x10104e7
     field public static final int contextDescription = 16844078; // 0x101052e
     field public static final int contextPopupMenuStyle = 16844033; // 0x1010501
@@ -1602,6 +1603,7 @@
     field public static final int supportedTypes = 16844369; // 0x1010651
     field public static final int supportsAssist = 16844016; // 0x10104f0
     field public static final int supportsBatteryGameMode = 16844374; // 0x1010656
+    field @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public static final int supportsConnectionlessStylusHandwriting;
     field public static final int supportsInlineSuggestions = 16844301; // 0x101060d
     field public static final int supportsInlineSuggestionsWithTouchExploration = 16844397; // 0x101066d
     field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
@@ -5448,7 +5450,6 @@
   }
 
   @FlaggedApi("android.security.content_uri_permission_apis") public final class ComponentCaller {
-    ctor public ComponentCaller(@NonNull android.os.IBinder, @Nullable android.os.IBinder);
     method public int checkContentUriPermission(@NonNull android.net.Uri, int);
     method @Nullable public String getPackage();
     method public int getUid();
@@ -7425,6 +7426,7 @@
     method public int onStartCommand(android.content.Intent, int, int);
     method public void onTaskRemoved(android.content.Intent);
     method public void onTimeout(int);
+    method @FlaggedApi("android.app.introduce_new_service_ontimeout_callback") public void onTimeout(int, int);
     method public void onTrimMemory(int);
     method public boolean onUnbind(android.content.Intent);
     method public final void startForeground(int, android.app.Notification);
@@ -7840,6 +7842,7 @@
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DeviceAdminInfo> CREATOR;
     field public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1; // 0x1
+    field @FlaggedApi("android.app.admin.flags.headless_device_owner_single_user_enabled") public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2; // 0x2
     field public static final int HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED = 0; // 0x0
     field public static final int USES_ENCRYPTED_STORAGE = 7; // 0x7
     field public static final int USES_POLICY_DISABLE_CAMERA = 8; // 0x8
@@ -10044,11 +10047,22 @@
     method public CharSequence coerceToText(android.content.Context);
     method public String getHtmlText();
     method public android.content.Intent getIntent();
+    method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @Nullable public android.app.PendingIntent getPendingIntent();
     method public CharSequence getText();
     method @Nullable public android.view.textclassifier.TextLinks getTextLinks();
     method public android.net.Uri getUri();
   }
 
+  @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final class ClipData.Item.Builder {
+    ctor public ClipData.Item.Builder();
+    method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item build();
+    method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setHtmlText(@Nullable String);
+    method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setIntent(@Nullable android.content.Intent);
+    method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setPendingIntent(@Nullable android.app.PendingIntent);
+    method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setText(@Nullable CharSequence);
+    method @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") @NonNull public android.content.ClipData.Item.Builder setUri(@Nullable android.net.Uri);
+  }
+
   public class ClipDescription implements android.os.Parcelable {
     ctor public ClipDescription(CharSequence, String[]);
     ctor public ClipDescription(android.content.ClipDescription);
@@ -10713,6 +10727,7 @@
     field public static final String PERFORMANCE_HINT_SERVICE = "performance_hint";
     field public static final String POWER_SERVICE = "power";
     field public static final String PRINT_SERVICE = "print";
+    field @FlaggedApi("android.os.telemetry_apis_framework_initialization") public static final String PROFILING_SERVICE = "profiling";
     field public static final int RECEIVER_EXPORTED = 2; // 0x2
     field public static final int RECEIVER_NOT_EXPORTED = 4; // 0x4
     field public static final int RECEIVER_VISIBLE_TO_INSTANT_APPS = 1; // 0x1
@@ -11303,10 +11318,14 @@
     field public static final String EXTRA_CHANGED_COMPONENT_NAME_LIST = "android.intent.extra.changed_component_name_list";
     field public static final String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list";
     field public static final String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list";
+    field @FlaggedApi("android.service.chooser.chooser_payload_toggling") public static final String EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI = "android.intent.extra.CHOOSER_ADDITIONAL_CONTENT_URI";
     field @FlaggedApi("android.service.chooser.chooser_album_text") public static final String EXTRA_CHOOSER_CONTENT_TYPE_HINT = "android.intent.extra.CHOOSER_CONTENT_TYPE_HINT";
     field public static final String EXTRA_CHOOSER_CUSTOM_ACTIONS = "android.intent.extra.CHOOSER_CUSTOM_ACTIONS";
+    field @FlaggedApi("android.service.chooser.chooser_payload_toggling") public static final String EXTRA_CHOOSER_FOCUSED_ITEM_POSITION = "android.intent.extra.CHOOSER_FOCUSED_ITEM_POSITION";
     field public static final String EXTRA_CHOOSER_MODIFY_SHARE_ACTION = "android.intent.extra.CHOOSER_MODIFY_SHARE_ACTION";
     field public static final String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER";
+    field @FlaggedApi("android.service.chooser.enable_chooser_result") public static final String EXTRA_CHOOSER_RESULT = "android.intent.extra.CHOOSER_RESULT";
+    field @FlaggedApi("android.service.chooser.enable_chooser_result") public static final String EXTRA_CHOOSER_RESULT_INTENT_SENDER = "android.intent.extra.CHOOSER_RESULT_INTENT_SENDER";
     field public static final String EXTRA_CHOOSER_TARGETS = "android.intent.extra.CHOOSER_TARGETS";
     field public static final String EXTRA_CHOSEN_COMPONENT = "android.intent.extra.CHOSEN_COMPONENT";
     field public static final String EXTRA_CHOSEN_COMPONENT_INTENT_SENDER = "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
@@ -11335,6 +11354,7 @@
     field public static final String EXTRA_LOCALE_LIST = "android.intent.extra.LOCALE_LIST";
     field public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY";
     field public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID";
+    field @FlaggedApi("android.service.chooser.enable_sharesheet_metadata_extra") public static final String EXTRA_METADATA_TEXT = "android.intent.extra.METADATA_TEXT";
     field public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES";
     field public static final String EXTRA_NOT_UNKNOWN_SOURCE = "android.intent.extra.NOT_UNKNOWN_SOURCE";
     field public static final String EXTRA_ORIGINATING_URI = "android.intent.extra.ORIGINATING_URI";
@@ -13025,6 +13045,7 @@
     field public static final String FEATURE_AUDIO_LOW_LATENCY = "android.hardware.audio.low_latency";
     field public static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
     field public static final String FEATURE_AUDIO_PRO = "android.hardware.audio.pro";
+    field @FlaggedApi("android.media.audio.feature_spatial_audio_headtracking_low_latency") public static final String FEATURE_AUDIO_SPATIAL_HEADTRACKING_LOW_LATENCY = "android.hardware.audio.spatial.headtracking.low_latency";
     field public static final String FEATURE_AUTOFILL = "android.software.autofill";
     field public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
     field public static final String FEATURE_BACKUP = "android.software.backup";
@@ -18846,6 +18867,7 @@
     method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable public android.hardware.biometrics.PromptContentView getContentView();
     method @Nullable public CharSequence getDescription();
     method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.graphics.Bitmap getLogoBitmap();
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public String getLogoDescription();
     method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public int getLogoRes();
     method @Nullable public CharSequence getNegativeButtonText();
     method @Nullable public CharSequence getSubtitle();
@@ -18897,6 +18919,7 @@
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
     method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean);
     method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoBitmap(@NonNull android.graphics.Bitmap);
+    method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoDescription(@NonNull String);
     method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission(android.Manifest.permission.SET_BIOMETRIC_DIALOG_LOGO) public android.hardware.biometrics.BiometricPrompt.Builder setLogoRes(@DrawableRes int);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
     method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence);
@@ -27798,7 +27821,7 @@
 
 package android.media.tv.ad {
 
-  @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public class TvAdManager {
+  @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") public final class TvAdManager {
     method @NonNull public java.util.List<android.media.tv.ad.TvAdServiceInfo> getTvAdServiceList();
     method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdManager.TvAdServiceCallback);
     method public void sendAppLinkCommand(@NonNull String, @NonNull android.os.Bundle);
@@ -27809,6 +27832,14 @@
     field public static final String APP_LINK_KEY_COMMAND_TYPE = "command_type";
     field public static final String APP_LINK_KEY_PACKAGE_NAME = "package_name";
     field public static final String APP_LINK_KEY_SERVICE_ID = "service_id";
+    field public static final int ERROR_BLOCKED = 5; // 0x5
+    field public static final int ERROR_ENCRYPTED = 6; // 0x6
+    field public static final int ERROR_NONE = 0; // 0x0
+    field public static final int ERROR_NOT_SUPPORTED = 2; // 0x2
+    field public static final int ERROR_RESOURCE_UNAVAILABLE = 4; // 0x4
+    field public static final int ERROR_UNKNOWN = 1; // 0x1
+    field public static final int ERROR_UNKNOWN_CHANNEL = 7; // 0x7
+    field public static final int ERROR_WEAK_SIGNAL = 3; // 0x3
     field public static final String INTENT_KEY_AD_SERVICE_ID = "ad_service_id";
     field public static final String INTENT_KEY_CHANNEL_URI = "channel_uri";
     field public static final String INTENT_KEY_COMMAND_TYPE = "command_type";
@@ -27821,6 +27852,9 @@
     field public static final String SESSION_DATA_TYPE_AD_REQUEST = "ad_request";
     field public static final String SESSION_DATA_TYPE_BROADCAST_INFO_REQUEST = "broadcast_info_request";
     field public static final String SESSION_DATA_TYPE_REMOVE_BROADCAST_INFO_REQUEST = "remove_broadcast_info_request";
+    field public static final int SESSION_STATE_ERROR = 3; // 0x3
+    field public static final int SESSION_STATE_RUNNING = 2; // 0x2
+    field public static final int SESSION_STATE_STOPPED = 1; // 0x1
   }
 
   public abstract static class TvAdManager.TvAdServiceCallback {
@@ -27843,7 +27877,12 @@
     ctor public TvAdService.Session(@NonNull android.content.Context);
     method public boolean isMediaViewEnabled();
     method @CallSuper public void layoutSurface(int, int, int, int);
+    method @CallSuper public void notifySessionStateChanged(int, int);
     method @Nullable public android.view.View onCreateMediaView();
+    method public void onCurrentChannelUri(@Nullable android.net.Uri);
+    method public void onCurrentTvInputId(@Nullable String);
+    method public void onCurrentVideoBounds(@NonNull android.graphics.Rect);
+    method public void onError(@NonNull String, @NonNull android.os.Bundle);
     method public boolean onGenericMotionEvent(@NonNull android.view.MotionEvent);
     method public boolean onKeyDown(int, @Nullable android.view.KeyEvent);
     method public boolean onKeyLongPress(int, @Nullable android.view.KeyEvent);
@@ -27853,12 +27892,20 @@
     method public abstract void onRelease();
     method public void onResetAdService();
     method public abstract boolean onSetSurface(@Nullable android.view.Surface);
+    method public void onSigningResult(@NonNull String, @NonNull byte[]);
     method public void onStartAdService();
     method public void onStopAdService();
     method public void onSurfaceChanged(int, int, int);
     method public boolean onTouchEvent(@NonNull android.view.MotionEvent);
+    method public void onTrackInfoList(@NonNull java.util.List<android.media.tv.TvTrackInfo>);
     method public boolean onTrackballEvent(@NonNull android.view.MotionEvent);
     method public void onTvInputSessionData(@NonNull String, @NonNull android.os.Bundle);
+    method public void onTvMessage(int, @NonNull android.os.Bundle);
+    method @CallSuper public void requestCurrentChannelUri();
+    method @CallSuper public void requestCurrentTvInputId();
+    method @CallSuper public void requestCurrentVideoBounds();
+    method @CallSuper public void requestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+    method @CallSuper public void requestTrackInfoList();
     method public void sendTvAdSessionData(@NonNull String, @NonNull android.os.Bundle);
     method @CallSuper public void setMediaViewEnabled(boolean);
   }
@@ -27877,9 +27924,12 @@
     ctor public TvAdView(@NonNull android.content.Context);
     ctor public TvAdView(@NonNull android.content.Context, @Nullable android.util.AttributeSet);
     ctor public TvAdView(@NonNull android.content.Context, @Nullable android.util.AttributeSet, int);
+    method public void clearCallback();
     method public void clearOnUnhandledInputEventListener();
     method public boolean dispatchUnhandledInputEvent(@NonNull android.view.InputEvent);
     method @Nullable public android.media.tv.ad.TvAdView.OnUnhandledInputEventListener getOnUnhandledInputEventListener();
+    method public void notifyError(@NonNull String, @NonNull android.os.Bundle);
+    method public void notifyTvMessage(@NonNull int, @NonNull android.os.Bundle);
     method public void onAttachedToWindow();
     method public void onDetachedFromWindow();
     method public void onLayout(boolean, int, int, int, int);
@@ -27889,16 +27939,34 @@
     method public void prepareAdService(@NonNull String, @NonNull String);
     method public void reset();
     method public void resetAdService();
+    method public void sendCurrentChannelUri(@Nullable android.net.Uri);
+    method public void sendCurrentTvInputId(@Nullable String);
+    method public void sendCurrentVideoBounds(@NonNull android.graphics.Rect);
+    method public void sendSigningResult(@NonNull String, @NonNull byte[]);
+    method public void sendTrackInfoList(@Nullable java.util.List<android.media.tv.TvTrackInfo>);
+    method public void setCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.TvAdCallback);
     method public void setOnUnhandledInputEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.ad.TvAdView.OnUnhandledInputEventListener);
     method public boolean setTvView(@Nullable android.media.tv.TvView);
     method public void startAdService();
     method public void stopAdService();
+    field public static final String ERROR_KEY_ERROR_CODE = "error_code";
+    field public static final String ERROR_KEY_METHOD_NAME = "method_name";
   }
 
   public static interface TvAdView.OnUnhandledInputEventListener {
     method public boolean onUnhandledInputEvent(@NonNull android.view.InputEvent);
   }
 
+  public abstract static class TvAdView.TvAdCallback {
+    ctor public TvAdView.TvAdCallback();
+    method public void onRequestCurrentChannelUri(@NonNull String);
+    method public void onRequestCurrentTvInputId(@NonNull String);
+    method public void onRequestCurrentVideoBounds(@NonNull String);
+    method public void onRequestSigning(@NonNull String, @NonNull String, @NonNull String, @NonNull String, @NonNull byte[]);
+    method public void onRequestTrackInfoList(@NonNull String);
+    method public void onStateChanged(@NonNull String, int, int);
+  }
+
 }
 
 package android.media.tv.interactive {
@@ -35472,6 +35540,7 @@
     ctor public CallLog.Calls();
     method public static String getLastOutgoingCall(android.content.Context);
     field public static final int ANSWERED_EXTERNALLY_TYPE = 7; // 0x7
+    field @FlaggedApi("com.android.server.telecom.flags.business_call_composer") public static final String ASSERTED_DISPLAY_NAME = "asserted_display_name";
     field public static final long AUTO_MISSED_EMERGENCY_CALL = 1L; // 0x1L
     field public static final long AUTO_MISSED_MAXIMUM_DIALING = 4L; // 0x4L
     field public static final long AUTO_MISSED_MAXIMUM_RINGING = 2L; // 0x2L
@@ -35518,6 +35587,7 @@
     field public static final int FEATURES_WIFI = 8; // 0x8
     field public static final String GEOCODED_LOCATION = "geocoded_location";
     field public static final int INCOMING_TYPE = 1; // 0x1
+    field @FlaggedApi("com.android.server.telecom.flags.business_call_composer") public static final String IS_BUSINESS_CALL = "is_business_call";
     field public static final String IS_READ = "is_read";
     field public static final String LAST_MODIFIED = "last_modified";
     field public static final String LIMIT_PARAM_KEY = "limit";
@@ -40217,6 +40287,21 @@
 
 package android.service.chooser {
 
+  @FlaggedApi("android.service.chooser.chooser_payload_toggling") public interface AdditionalContentContract {
+  }
+
+  public static interface AdditionalContentContract.Columns {
+    field public static final String URI = "uri";
+  }
+
+  public static interface AdditionalContentContract.CursorExtraKeys {
+    field public static final String POSITION = "position";
+  }
+
+  public static interface AdditionalContentContract.MethodNames {
+    field public static final String ON_SELECTION_CHANGED = "onSelectionChanged";
+  }
+
   public final class ChooserAction implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.app.PendingIntent getAction();
@@ -40231,6 +40316,19 @@
     method @NonNull public android.service.chooser.ChooserAction build();
   }
 
+  @FlaggedApi("android.service.chooser.enable_chooser_result") public final class ChooserResult implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.content.ComponentName getSelectedComponent();
+    method public int getType();
+    method public boolean isShortcut();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CHOOSER_RESULT_COPY = 1; // 0x1
+    field public static final int CHOOSER_RESULT_EDIT = 2; // 0x2
+    field public static final int CHOOSER_RESULT_SELECTED_COMPONENT = 0; // 0x0
+    field public static final int CHOOSER_RESULT_UNKNOWN = -1; // 0xffffffff
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.chooser.ChooserResult> CREATOR;
+  }
+
   @Deprecated public final class ChooserTarget implements android.os.Parcelable {
     ctor @Deprecated public ChooserTarget(CharSequence, android.graphics.drawable.Icon, float, android.content.ComponentName, @Nullable android.os.Bundle);
     method @Deprecated public int describeContents();
@@ -41995,8 +42093,10 @@
     field @Deprecated public static final String AVAILABLE_PHONE_ACCOUNTS = "selectPhoneAccountAccounts";
     field public static final String EVENT_CLEAR_DIAGNOSTIC_MESSAGE = "android.telecom.event.CLEAR_DIAGNOSTIC_MESSAGE";
     field public static final String EVENT_DISPLAY_DIAGNOSTIC_MESSAGE = "android.telecom.event.DISPLAY_DIAGNOSTIC_MESSAGE";
+    field @FlaggedApi("com.android.server.telecom.flags.business_call_composer") public static final String EXTRA_ASSERTED_DISPLAY_NAME = "android.telecom.extra.ASSERTED_DISPLAY_NAME";
     field public static final String EXTRA_DIAGNOSTIC_MESSAGE = "android.telecom.extra.DIAGNOSTIC_MESSAGE";
     field public static final String EXTRA_DIAGNOSTIC_MESSAGE_ID = "android.telecom.extra.DIAGNOSTIC_MESSAGE_ID";
+    field @FlaggedApi("com.android.server.telecom.flags.business_call_composer") public static final String EXTRA_IS_BUSINESS_CALL = "android.telecom.extra.IS_BUSINESS_CALL";
     field public static final String EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB = "android.telecom.extra.IS_SUPPRESSED_BY_DO_NOT_DISTURB";
     field public static final String EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS = "android.telecom.extra.LAST_EMERGENCY_CALLBACK_TIME_MILLIS";
     field public static final String EXTRA_SILENT_RINGING_REQUESTED = "android.telecom.extra.SILENT_RINGING_REQUESTED";
@@ -43624,6 +43724,7 @@
     field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
     field public static final String KEY_SUBSCRIPTION_GROUP_UUID_STRING = "subscription_group_uuid_string";
     field public static final String KEY_SUPPORTED_PREMIUM_CAPABILITIES_INT_ARRAY = "supported_premium_capabilities_int_array";
+    field @FlaggedApi("com.android.server.telecom.flags.business_call_composer") public static final String KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL = "supports_business_call_composer_bool";
     field public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool";
     field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL = "supports_device_to_device_communication_using_dtmf_bool";
     field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_RTP_BOOL = "supports_device_to_device_communication_using_rtp_bool";
@@ -45889,6 +45990,7 @@
     field public static final int AUTHTYPE_EAP_SIM = 128; // 0x80
     field public static final int AUTHTYPE_GBA_BOOTSTRAP = 132; // 0x84
     field public static final int AUTHTYPE_GBA_NAF_KEY_EXTERNAL = 133; // 0x85
+    field @FlaggedApi("com.android.server.telecom.flags.business_call_composer") public static final int CALL_COMPOSER_STATUS_BUSINESS_ONLY = 2; // 0x2
     field public static final int CALL_COMPOSER_STATUS_OFF = 0; // 0x0
     field public static final int CALL_COMPOSER_STATUS_ON = 1; // 0x1
     field public static final int CALL_STATE_IDLE = 0; // 0x0
@@ -46925,6 +47027,7 @@
   public static class MmTelFeature.MmTelCapabilities {
     method public final boolean isCapable(int);
     field public static final int CAPABILITY_TYPE_CALL_COMPOSER = 16; // 0x10
+    field @FlaggedApi("com.android.server.telecom.flags.business_call_composer") public static final int CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY = 32; // 0x20
     field public static final int CAPABILITY_TYPE_SMS = 8; // 0x8
     field public static final int CAPABILITY_TYPE_UT = 4; // 0x4
     field public static final int CAPABILITY_TYPE_VIDEO = 2; // 0x2
@@ -52900,9 +53003,11 @@
     field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
     field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
     field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
+    field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_GLOBAL_SAME_APPLICATION = 4096; // 0x1000
     field public static final int DRAG_FLAG_GLOBAL_URI_READ = 1; // 0x1
     field public static final int DRAG_FLAG_GLOBAL_URI_WRITE = 2; // 0x2
     field public static final int DRAG_FLAG_OPAQUE = 512; // 0x200
+    field @FlaggedApi("com.android.window.flags.delegate_unhandled_drags") public static final int DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG = 8192; // 0x2000
     field @Deprecated public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
     field @Deprecated public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
     field @Deprecated public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
@@ -53992,8 +54097,11 @@
     method @Deprecated @NonNull public android.view.WindowInsets consumeDisplayCutout();
     method @Deprecated @NonNull public android.view.WindowInsets consumeStableInsets();
     method @Deprecated @NonNull public android.view.WindowInsets consumeSystemWindowInsets();
+    method @FlaggedApi("android.view.flags.customizable_window_headers") @NonNull public java.util.List<android.graphics.Rect> getBoundingRects(int);
+    method @FlaggedApi("android.view.flags.customizable_window_headers") @NonNull public java.util.List<android.graphics.Rect> getBoundingRectsIgnoringVisibility(int);
     method @Nullable public android.view.DisplayCutout getDisplayCutout();
     method @Nullable public android.view.DisplayShape getDisplayShape();
+    method @FlaggedApi("android.view.flags.customizable_window_headers") @NonNull public android.util.Size getFrame();
     method @NonNull public android.graphics.Insets getInsets(int);
     method @NonNull public android.graphics.Insets getInsetsIgnoringVisibility(int);
     method @Deprecated @NonNull public android.graphics.Insets getMandatorySystemGestureInsets();
@@ -54028,8 +54136,11 @@
     ctor public WindowInsets.Builder();
     ctor public WindowInsets.Builder(@NonNull android.view.WindowInsets);
     method @NonNull public android.view.WindowInsets build();
+    method @FlaggedApi("android.view.flags.customizable_window_headers") @NonNull public android.view.WindowInsets.Builder setBoundingRects(int, @NonNull java.util.List<android.graphics.Rect>);
+    method @FlaggedApi("android.view.flags.customizable_window_headers") @NonNull public android.view.WindowInsets.Builder setBoundingRectsIgnoringVisibility(int, @NonNull java.util.List<android.graphics.Rect>);
     method @NonNull public android.view.WindowInsets.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
     method @NonNull public android.view.WindowInsets.Builder setDisplayShape(@NonNull android.view.DisplayShape);
+    method @FlaggedApi("android.view.flags.customizable_window_headers") @NonNull public android.view.WindowInsets.Builder setFrame(int, int);
     method @NonNull public android.view.WindowInsets.Builder setInsets(int, @NonNull android.graphics.Insets);
     method @NonNull public android.view.WindowInsets.Builder setInsetsIgnoringVisibility(int, @NonNull android.graphics.Insets) throws java.lang.IllegalArgumentException;
     method @Deprecated @NonNull public android.view.WindowInsets.Builder setMandatorySystemGestureInsets(@NonNull android.graphics.Insets);
@@ -56002,6 +56113,7 @@
     method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
     method public CharSequence loadLabel(android.content.pm.PackageManager);
     method public boolean shouldShowInInputMethodPicker();
+    method @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public boolean supportsConnectionlessStylusHandwriting();
     method public boolean supportsStylusHandwriting();
     method public boolean suppressesSpellChecker();
     method public void writeToParcel(android.os.Parcel, int);
@@ -56031,6 +56143,7 @@
     method public boolean isAcceptingText();
     method public boolean isActive(android.view.View);
     method public boolean isActive();
+    method @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public boolean isConnectionlessStylusHandwritingAvailable();
     method public boolean isFullscreenMode();
     method public boolean isInputMethodSuppressingSpellChecker();
     method public boolean isStylusHandwritingAvailable();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 1273da7..af8b708 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -412,6 +412,19 @@
     field public static final int VPN_UID = 1016; // 0x3f8
   }
 
+  @FlaggedApi("android.os.telemetry_apis_framework_initialization") public class ProfilingServiceManager {
+    method @NonNull public android.os.ProfilingServiceManager.ServiceRegisterer getProfilingServiceRegisterer();
+  }
+
+  public static class ProfilingServiceManager.ServiceNotFoundException extends java.lang.Exception {
+    ctor public ProfilingServiceManager.ServiceNotFoundException(@NonNull String);
+  }
+
+  public static final class ProfilingServiceManager.ServiceRegisterer {
+    method @Nullable public android.os.IBinder get();
+    method @Nullable public android.os.IBinder getOrThrow() throws android.os.ProfilingServiceManager.ServiceNotFoundException;
+  }
+
   public final class ServiceManager {
     method @NonNull public static String[] getDeclaredInstances(@NonNull String);
     method public static boolean isDeclared(@NonNull String);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ecbdeaa..e44eb24 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -282,6 +282,7 @@
     field public static final String RADIO_SCAN_WITHOUT_LOCATION = "android.permission.RADIO_SCAN_WITHOUT_LOCATION";
     field public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission.READ_ACTIVE_EMERGENCY_SESSION";
     field public static final String READ_APP_SPECIFIC_LOCALES = "android.permission.READ_APP_SPECIFIC_LOCALES";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String READ_BLOCKED_NUMBERS = "android.permission.READ_BLOCKED_NUMBERS";
     field public static final String READ_CARRIER_APP_INFO = "android.permission.READ_CARRIER_APP_INFO";
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
     field public static final String READ_CLIPBOARD_IN_BACKGROUND = "android.permission.READ_CLIPBOARD_IN_BACKGROUND";
@@ -409,6 +410,7 @@
     field public static final String WIFI_UPDATE_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS";
     field public static final String WIFI_UPDATE_USABILITY_STATS_SCORE = "android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE";
     field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String WRITE_BLOCKED_NUMBERS = "android.permission.WRITE_BLOCKED_NUMBERS";
     field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
     field public static final String WRITE_DREAM_STATE = "android.permission.WRITE_DREAM_STATE";
     field public static final String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS";
@@ -1389,6 +1391,7 @@
     field public static final int STATUS_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
     field public static final int STATUS_HAS_DEVICE_OWNER = 1; // 0x1
     field public static final int STATUS_HAS_PAIRED = 8; // 0x8
+    field @FlaggedApi("android.app.admin.flags.headless_device_owner_single_user_enabled") public static final int STATUS_HEADLESS_ONLY_SYSTEM_USER = 17; // 0x11
     field public static final int STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED = 16; // 0x10
     field public static final int STATUS_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
     field public static final int STATUS_NONSYSTEM_USER_EXISTS = 5; // 0x5
@@ -2204,6 +2207,7 @@
     method public void notifyLaunchLocationShown(@NonNull String, @NonNull java.util.List<android.app.prediction.AppTargetId>);
     method public void registerPredictionUpdates(@NonNull java.util.concurrent.Executor, @NonNull android.app.prediction.AppPredictor.Callback);
     method public void requestPredictionUpdate();
+    method @FlaggedApi("android.service.appprediction.flags.service_features_api") public void requestServiceFeatures(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.os.Bundle>);
     method @Nullable public void sortTargets(@NonNull java.util.List<android.app.prediction.AppTarget>, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.prediction.AppTarget>>);
     method public void unregisterPredictionUpdates(@NonNull android.app.prediction.AppPredictor.Callback);
   }
@@ -6640,6 +6644,7 @@
     method public int getSupportedRoleCombinations();
     method public int getUsbDataStatus();
     method public boolean isConnected();
+    method @FlaggedApi("android.hardware.usb.flags.enable_is_pd_compliant_api") public boolean isPdCompliant();
     method public boolean isPowerTransferLimited();
     method public boolean isRoleCombinationSupported(int, int);
     method public void writeToParcel(android.os.Parcel, int);
@@ -11064,6 +11069,7 @@
     field public static final String USER_TYPE_FULL_SYSTEM = "android.os.usertype.full.SYSTEM";
     field public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE";
     field public static final String USER_TYPE_PROFILE_MANAGED = "android.os.usertype.profile.MANAGED";
+    field @FlaggedApi("android.os.allow_private_profile") public static final String USER_TYPE_PROFILE_PRIVATE = "android.os.usertype.profile.PRIVATE";
     field public static final String USER_TYPE_SYSTEM_HEADLESS = "android.os.usertype.system.HEADLESS";
   }
 
@@ -11454,6 +11460,29 @@
 
 package android.provider {
 
+  public static class BlockedNumberContract.BlockedNumbers {
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static void endBlockSuppression(@NonNull android.content.Context);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @NonNull @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static android.provider.BlockedNumberContract.BlockedNumbers.BlockSuppressionStatus getBlockSuppressionStatus(@NonNull android.content.Context);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static boolean getBlockedNumberSetting(@NonNull android.content.Context, @NonNull String);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static void notifyEmergencyContact(@NonNull android.content.Context);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static void setBlockedNumberSetting(@NonNull android.content.Context, @NonNull String, boolean);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static boolean shouldShowEmergencyCallNotification(@NonNull android.content.Context);
+    method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(allOf={android.Manifest.permission.READ_BLOCKED_NUMBERS, android.Manifest.permission.WRITE_BLOCKED_NUMBERS}) public static int shouldSystemBlockNumber(@NonNull android.content.Context, @NonNull String, int, boolean);
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ACTION_BLOCK_SUPPRESSION_STATE_CHANGED = "android.provider.action.BLOCK_SUPPRESSION_STATE_CHANGED";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_PAYPHONE = "block_payphone_calls_setting";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_PRIVATE = "block_private_number_calls_setting";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE = "block_unavailable_calls_setting";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_UNKNOWN = "block_unknown_calls_setting";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED = "block_numbers_not_in_contacts_setting";
+    field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION = "show_emergency_call_notification";
+  }
+
+  @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final class BlockedNumberContract.BlockedNumbers.BlockSuppressionStatus {
+    ctor public BlockedNumberContract.BlockedNumbers.BlockSuppressionStatus(boolean, long);
+    method public boolean getIsSuppressed();
+    method public long getUntilTimestampMillis();
+  }
+
   public class CallLog {
     method @RequiresPermission(allOf={android.Manifest.permission.WRITE_CALL_LOG, android.Manifest.permission.INTERACT_ACROSS_USERS}) public static void storeCallComposerPicture(@NonNull android.content.Context, @NonNull java.io.InputStream, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.Uri,android.provider.CallLog.CallComposerLoggingException>);
   }
@@ -12057,6 +12086,7 @@
     method @MainThread public void onDestroyPredictionSession(@NonNull android.app.prediction.AppPredictionSessionId);
     method @MainThread public abstract void onLaunchLocationShown(@NonNull android.app.prediction.AppPredictionSessionId, @NonNull String, @NonNull java.util.List<android.app.prediction.AppTargetId>);
     method @MainThread public abstract void onRequestPredictionUpdate(@NonNull android.app.prediction.AppPredictionSessionId);
+    method @FlaggedApi("android.service.appprediction.flags.service_features_api") @MainThread public void onRequestServiceFeatures(@NonNull android.app.prediction.AppPredictionSessionId, @NonNull java.util.function.Consumer<android.os.Bundle>);
     method @MainThread public abstract void onSortAppTargets(@NonNull android.app.prediction.AppPredictionSessionId, @NonNull java.util.List<android.app.prediction.AppTarget>, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<java.util.List<android.app.prediction.AppTarget>>);
     method @MainThread public void onStartPredictionUpdates();
     method @MainThread public void onStopPredictionUpdates();
@@ -16114,6 +16144,7 @@
     field public static final int DIALSTRING_USSD = 2; // 0x2
     field public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo";
     field public static final String EXTRA_ADDITIONAL_SIP_INVITE_FIELDS = "android.telephony.ims.extra.ADDITIONAL_SIP_INVITE_FIELDS";
+    field @FlaggedApi("com.android.server.telecom.flags.business_call_composer") public static final String EXTRA_ASSERTED_DISPLAY_NAME = "android.telephony.ims.extra.ASSERTED_DISPLAY_NAME";
     field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE";
     field public static final String EXTRA_CALL_NETWORK_TYPE = "android.telephony.ims.extra.CALL_NETWORK_TYPE";
     field @Deprecated public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 19b265d..5b90322 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2089,7 +2089,7 @@
 package android.media.projection {
 
   public final class MediaProjectionManager {
-    method @NonNull public android.content.Intent createScreenCaptureIntent(@Nullable android.app.ActivityOptions.LaunchCookie);
+    method @NonNull public android.content.Intent createScreenCaptureIntent(@NonNull android.app.ActivityOptions.LaunchCookie);
   }
 
 }
@@ -2462,7 +2462,6 @@
     method public boolean isVisibleBackgroundUsersOnDefaultDisplaySupported();
     method public boolean isVisibleBackgroundUsersSupported();
     method @Deprecated @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
-    field @FlaggedApi("android.os.allow_private_profile") public static final String USER_TYPE_PROFILE_PRIVATE = "android.os.usertype.profile.PRIVATE";
   }
 
   public final class VibrationAttributes implements android.os.Parcelable {
@@ -3902,7 +3901,7 @@
   }
 
   public final class InputMethodInfo implements android.os.Parcelable {
-    ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, @NonNull String);
+    ctor @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, boolean, @NonNull String);
     ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int);
     field public static final int COMPONENT_NAME_MAX_LENGTH = 1000; // 0x3e8
     field public static final int MAX_IMES_PER_PACKAGE = 20; // 0x14
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2c00c99..b25d5eb 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -146,6 +146,8 @@
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.Process;
+import android.os.ProfilingFrameworkInitializer;
+import android.os.ProfilingServiceManager;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -1232,6 +1234,15 @@
         }
 
         @Override
+        public final void scheduleTimeoutServiceForType(IBinder token, int startId, int fgsType) {
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                        "scheduleTimeoutServiceForType. token=" + token);
+            }
+            sendMessage(H.TIMEOUT_SERVICE_FOR_TYPE, token, startId, fgsType);
+        }
+
+        @Override
         public final void bindApplication(
                 String processName,
                 ApplicationInfo appInfo,
@@ -2288,6 +2299,8 @@
         public static final int INSTRUMENT_WITHOUT_RESTART = 170;
         public static final int FINISH_INSTRUMENTATION_WITHOUT_RESTART = 171;
 
+        public static final int TIMEOUT_SERVICE_FOR_TYPE = 172;
+
         String codeToString(int code) {
             if (DEBUG_MESSAGES) {
                 switch (code) {
@@ -2341,6 +2354,7 @@
                     case DUMP_RESOURCES: return "DUMP_RESOURCES";
                     case TIMEOUT_SERVICE: return "TIMEOUT_SERVICE";
                     case PING: return "PING";
+                    case TIMEOUT_SERVICE_FOR_TYPE: return "TIMEOUT_SERVICE_FOR_TYPE";
                 }
             }
             return Integer.toString(code);
@@ -2427,6 +2441,14 @@
                 case PING:
                     ((RemoteCallback) msg.obj).sendResult(null);
                     break;
+                case TIMEOUT_SERVICE_FOR_TYPE:
+                    if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                                "serviceTimeoutForType: " + msg.obj);
+                    }
+                    handleTimeoutServiceForType((IBinder) msg.obj, msg.arg1, msg.arg2);
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
                 case CONFIGURATION_CHANGED:
                     mConfigurationController.handleConfigurationChanged((Configuration) msg.obj);
                     break;
@@ -5136,6 +5158,26 @@
             Slog.wtf(TAG, "handleTimeoutService: token=" + token + " not found.");
         }
     }
+
+    private void handleTimeoutServiceForType(IBinder token, int startId, int fgsType) {
+        Service s = mServices.get(token);
+        if (s != null) {
+            try {
+                if (localLOGV) Slog.v(TAG, "Timeout service " + s);
+
+                s.callOnTimeLimitExceeded(startId, fgsType);
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(s, e)) {
+                    throw new RuntimeException(
+                            "Unable to call onTimeLimitExceeded on service " + s + ": " + e, e);
+                }
+                Slog.i(TAG, "handleTimeoutServiceForType: exception for " + token, e);
+            }
+        } else {
+            Slog.wtf(TAG, "handleTimeoutServiceForType: token=" + token + " not found.");
+        }
+    }
+
     /**
      * Resume the activity.
      * @param r Target activity record.
@@ -8548,6 +8590,9 @@
         NfcFrameworkInitializer.setNfcServiceManager(new NfcServiceManager());
         DeviceConfigInitializer.setDeviceConfigServiceManager(new DeviceConfigServiceManager());
         SeFrameworkInitializer.setSeServiceManager(new SeServiceManager());
+        if (android.server.Flags.telemetryApisService()) {
+            ProfilingFrameworkInitializer.setProfilingServiceManager(new ProfilingServiceManager());
+        }
     }
 
     private void purgePendingResources() {
diff --git a/core/java/android/app/ComponentCaller.java b/core/java/android/app/ComponentCaller.java
index a440dbc..44e8a0a 100644
--- a/core/java/android/app/ComponentCaller.java
+++ b/core/java/android/app/ComponentCaller.java
@@ -42,6 +42,9 @@
     private final IBinder mActivityToken;
     private final IBinder mCallerToken;
 
+    /**
+     * @hide
+     */
     public ComponentCaller(@NonNull IBinder activityToken, @Nullable IBinder callerToken) {
         mActivityToken = activityToken;
         mCallerToken = callerToken;
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index ceeaf5d..cc0aafd 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -942,6 +942,8 @@
 
     /** Returns if the service is a short-service is still "alive" and past the timeout. */
     boolean shouldServiceTimeOut(in ComponentName className, in IBinder token);
+    /** Returns if the service has a time-limit restricted type and is past the time limit. */
+    boolean hasServiceTimeLimitExceeded(in ComponentName className, in IBinder token);
 
     void registerUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback);
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 59e0e99..a04620c 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -178,5 +178,6 @@
             in TranslationSpec targetSpec, in List<AutofillId> viewIds,
             in UiTranslationSpec uiTranslationSpec);
     void scheduleTimeoutService(IBinder token, int startId);
+    void scheduleTimeoutServiceForType(IBinder token, int startId, int fgsType);
     void schedulePing(in RemoteCallback pong);
 }
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index a155457..d470299 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -20,6 +20,7 @@
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.text.TextUtils.formatSimple;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1161,4 +1162,37 @@
      */
     public void onTimeout(int startId) {
     }
+
+    /** @hide */
+    public final void callOnTimeLimitExceeded(int startId, int fgsType) {
+        // Note, because all the service callbacks (and other similar callbacks, e.g. activity
+        // callbacks) are delivered using the main handler, it's possible the service is already
+        // stopped when before this method is called, so we do a double check here.
+        if (mToken == null) {
+            Log.w(TAG, "Service already destroyed, skipping onTimeLimitExceeded()");
+            return;
+        }
+        try {
+            if (!mActivityManager.hasServiceTimeLimitExceeded(
+                    new ComponentName(this, mClassName), mToken)) {
+                Log.w(TAG, "Service no longer relevant, skipping onTimeLimitExceeded()");
+                return;
+            }
+        } catch (RemoteException ex) {
+        }
+        if (Flags.introduceNewServiceOntimeoutCallback()) {
+            onTimeout(startId, fgsType);
+        }
+    }
+
+    /**
+     * Callback called when a particular foreground service type has timed out.
+     *
+     * @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when
+     * the service started.
+     * @param fgsType the foreground service type which caused the timeout.
+     */
+    @FlaggedApi(Flags.FLAG_INTRODUCE_NEW_SERVICE_ONTIMEOUT_CALLBACK)
+    public void onTimeout(int startId, int fgsType) {
+    }
 }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index ba9c895..9d35dc3 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -186,6 +186,7 @@
 import android.os.PerformanceHintManager;
 import android.os.PermissionEnforcer;
 import android.os.PowerManager;
+import android.os.ProfilingFrameworkInitializer;
 import android.os.RecoverySystem;
 import android.os.SecurityStateManager;
 import android.os.ServiceManager;
@@ -1662,6 +1663,9 @@
             if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()) {
                 EnhancedConfirmationFrameworkInitializer.registerServiceWrappers();
             }
+            if (android.server.Flags.telemetryApisService()) {
+                ProfilingFrameworkInitializer.registerServiceWrappers();
+            }
         } finally {
             // If any of the above code throws, we're in a pretty bad shape and the process
             // will likely crash, but we'll reset it just in case there's an exception handler...
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index c0b299b..ff23f09 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -27,3 +27,10 @@
      description: "API to add OnUidImportanceListener with targetted UIDs"
      bug: "286258140"
 }
+
+flag {
+     namespace: "backstage_power"
+     name: "introduce_new_service_ontimeout_callback"
+     description: "Add a new callback in Service to indicate a FGS has reached its timeout."
+     bug: "317799821"
+}
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 14462b8..7d5d5c1 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -16,8 +16,10 @@
 
 package android.app.admin;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.app.admin.flags.Flags;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -176,7 +178,18 @@
      */
     public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1;
 
-    @IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED})
+    /**
+     * Value for {@link #getHeadlessDeviceOwnerMode} which indicates that this DPC should be
+     * provisioned into the first secondary user when on a Headless System User Mode device.
+     *
+     * <p>This mode only allows a single secondary user on the device blocking the creation of
+     * additional secondary users.
+     */
+    @FlaggedApi(Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
+    public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2;
+
+    @IntDef({HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED, HEADLESS_DEVICE_OWNER_MODE_AFFILIATED,
+            HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER})
     @Retention(RetentionPolicy.SOURCE)
     private @interface HeadlessDeviceOwnerMode {}
 
@@ -373,6 +386,8 @@
                         mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED;
                     } else if (deviceOwnerModeStringValue.equalsIgnoreCase("affiliated")) {
                         mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
+                    } else if (deviceOwnerModeStringValue.equalsIgnoreCase("single_user")) {
+                        mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
                     } else {
                         throw new XmlPullParserException("headless-system-user mode must be valid");
                     }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c8762c6..c649e62 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -84,6 +84,7 @@
 import android.app.IServiceConnection;
 import android.app.KeyguardManager;
 import android.app.admin.SecurityLog.SecurityEvent;
+import android.app.admin.flags.Flags;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
@@ -2863,6 +2864,19 @@
     public static final int STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED = 16;
 
     /**
+     * Result code for {@link #checkProvisioningPrecondition}.
+     *
+     * <p>Returned for {@link #ACTION_PROVISION_MANAGED_DEVICE} when provisioning a DPC into the
+     * {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER} mode but only the system
+     * user exists on the device.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED)
+    public static final int STATUS_HEADLESS_ONLY_SYSTEM_USER = 17;
+
+    /**
      * Result codes for {@link #checkProvisioningPrecondition} indicating all the provisioning pre
      * conditions.
      *
@@ -2876,7 +2890,7 @@
             STATUS_CANNOT_ADD_MANAGED_PROFILE, STATUS_DEVICE_ADMIN_NOT_SUPPORTED,
             STATUS_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER,
             STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS,
-            STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED
+            STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED, STATUS_HEADLESS_ONLY_SYSTEM_USER
     })
     public @interface ProvisioningPrecondition {}
 
@@ -9178,9 +9192,11 @@
      * <p>Calling this after the setup phase of the device owner user has completed is allowed only
      * if the caller is the {@link Process#SHELL_UID Shell UID}, and there are no additional users
      * (except when the device runs on headless system user mode, in which case it could have exact
-     * one extra user, which is the current user - the device owner will be set in the
-     * {@link UserHandle#SYSTEM system} user and a profile owner will be set in the current user)
-     * and no accounts.
+     * one extra user, which is the current user.
+     *
+     * <p>On a headless devices, if it is in affiliated mode the device owner will be set in the
+     * {@link UserHandle#SYSTEM system} user. If the device is in single user mode, the device owner
+     * will be set in the first secondary user.
      *
      * @param who the component name to be registered as device owner.
      * @param userId ID of the user on which the device owner runs.
@@ -11371,7 +11387,9 @@
      * @see UserHandle
      * @return the {@link android.os.UserHandle} object for the created user, or {@code null} if the
      *         user could not be created.
-     * @throws SecurityException if {@code admin} is not a device owner.
+     * @throws SecurityException if headless device is in
+     *        {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER} mode.
+     * @throws SecurityException if {@code admin} is not a device owner
      * @throws UserOperationException if the user could not be created and the calling app is
      * targeting {@link android.os.Build.VERSION_CODES#P} and running on
      * {@link android.os.Build.VERSION_CODES#P}.
@@ -16612,7 +16630,10 @@
      * before calling this method.
      *
      * <p>Holders of {@link android.Manifest.permission#PROVISION_DEMO_DEVICE} can call this API
-     * only if {@link FullyManagedDeviceProvisioningParams#isDemoDevice()} is {@code true}.</p>
+     * only if {@link FullyManagedDeviceProvisioningParams#isDemoDevice()} is {@code true}.
+     *
+     * <p>If headless device is in {@link DeviceAdminInfo#HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER}
+     * mode then it sets the device owner on the first secondary user.</p>
      *
      * @param provisioningParams Params required to provision a fully managed device,
      * see {@link FullyManagedDeviceProvisioningParams}.
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 6cc8af8..3c98ef9 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -76,3 +76,10 @@
   description: "Enable APIs to provision and manage eSIMs"
   bug: "295301164"
 }
+
+flag {
+  name: "headless_device_owner_single_user_enabled"
+  namespace: "enterprise"
+  description: "Add Headless DO support."
+  bug: "289515470"
+}
diff --git a/core/java/android/app/prediction/AppPredictor.java b/core/java/android/app/prediction/AppPredictor.java
index d628b7f..0c1a28a 100644
--- a/core/java/android/app/prediction/AppPredictor.java
+++ b/core/java/android/app/prediction/AppPredictor.java
@@ -16,6 +16,7 @@
 package android.app.prediction;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -24,9 +25,12 @@
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.service.appprediction.flags.Flags;
 import android.util.ArrayMap;
 import android.util.Log;
 
@@ -263,6 +267,34 @@
     }
 
     /**
+     * Requests a Bundle which includes service features info or {@code null} if the service is not
+     * available.
+     *
+     * @param callbackExecutor The callback executor to use when calling the callback. It cannot be
+     *                        null.
+     * @param callback The callback to return the Bundle which includes service features info. It
+     *                cannot be null.
+     *
+     * @throws IllegalStateException If this AppPredictor has already been destroyed.
+     * @throws RuntimeException If there is a failure communicating with the remote service.
+     */
+    @FlaggedApi(Flags.FLAG_SERVICE_FEATURES_API)
+    public void requestServiceFeatures(@NonNull Executor callbackExecutor,
+            @NonNull Consumer<Bundle> callback) {
+        if (mIsClosed.get()) {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+
+        try {
+            mPredictionManager.requestServiceFeatures(mSessionId,
+                    new RemoteCallbackWrapper(callbackExecutor, callback));
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to request service feature info", e);
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
      * Destroys the client and unregisters the callback. Any method on this class after this call
      * with throw {@link IllegalStateException}.
      */
@@ -347,6 +379,28 @@
         }
     }
 
+    static class RemoteCallbackWrapper extends IRemoteCallback.Stub {
+
+        private final Consumer<Bundle> mCallback;
+        private final Executor mExecutor;
+
+        RemoteCallbackWrapper(@NonNull Executor callbackExecutor,
+                @NonNull Consumer<Bundle> callback) {
+            mExecutor = callbackExecutor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void sendResult(Bundle result) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.accept(result));
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
     private static class Token {
         static final IBinder sBinder = new Binder(TAG);
     }
diff --git a/core/java/android/app/prediction/IPredictionManager.aidl b/core/java/android/app/prediction/IPredictionManager.aidl
index 863fc6f9..94b4f5b 100644
--- a/core/java/android/app/prediction/IPredictionManager.aidl
+++ b/core/java/android/app/prediction/IPredictionManager.aidl
@@ -22,6 +22,7 @@
 import android.app.prediction.AppPredictionSessionId;
 import android.app.prediction.IPredictionCallback;
 import android.content.pm.ParceledListSlice;
+import android.os.IRemoteCallback;
 
 /**
  * @hide
@@ -48,4 +49,6 @@
     void requestPredictionUpdate(in AppPredictionSessionId sessionId);
 
     void onDestroyPredictionSession(in AppPredictionSessionId sessionId);
+
+    void requestServiceFeatures(in AppPredictionSessionId sessionId, in IRemoteCallback callback);
 }
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 67759f4..eb357fe 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -21,7 +21,13 @@
 import static android.content.ContentResolver.SCHEME_CONTENT;
 import static android.content.ContentResolver.SCHEME_FILE;
 
+import static com.android.window.flags.Flags.FLAG_DELEGATE_UNHANDLED_DRAGS;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.PendingIntent;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.res.AssetFileDescriptor;
@@ -207,6 +213,7 @@
         final CharSequence mText;
         final String mHtmlText;
         final Intent mIntent;
+        final PendingIntent mPendingIntent;
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
         Uri mUri;
         private TextLinks mTextLinks;
@@ -214,12 +221,91 @@
         // if the data is obtained from {@link #copyForTransferWithActivityInfo}
         private ActivityInfo mActivityInfo;
 
+        /**
+         * A builder for a ClipData Item.
+         */
+        @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+        @SuppressLint("PackageLayering")
+        public static final class Builder {
+            private CharSequence mText;
+            private String mHtmlText;
+            private Intent mIntent;
+            private PendingIntent mPendingIntent;
+            private Uri mUri;
+
+            /**
+             * Sets the text for the item to be constructed.
+             */
+            @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+            @NonNull
+            public Builder setText(@Nullable CharSequence text) {
+                mText = text;
+                return this;
+            }
+
+            /**
+             * Sets the HTML text for the item to be constructed.
+             */
+            @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+            @NonNull
+            public Builder setHtmlText(@Nullable String htmlText) {
+                mHtmlText = htmlText;
+                return this;
+            }
+
+            /**
+             * Sets the Intent for the item to be constructed.
+             */
+            @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+            @NonNull
+            public Builder setIntent(@Nullable Intent intent) {
+                mIntent = intent;
+                return this;
+            }
+
+            /**
+             * Sets the PendingIntent for the item to be constructed. To prevent receiving apps from
+             * improperly manipulating the intent to launch another activity as this caller, the
+             * provided PendingIntent must be immutable (see {@link PendingIntent#FLAG_IMMUTABLE}).
+             * The system will clean up the PendingIntent when it is no longer used.
+             */
+            @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+            @NonNull
+            public Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
+                if (pendingIntent != null && !pendingIntent.isImmutable()) {
+                    throw new IllegalArgumentException("Expected pending intent to be immutable");
+                }
+                mPendingIntent = pendingIntent;
+                return this;
+            }
+
+            /**
+             * Sets the URI for the item to be constructed.
+             */
+            @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+            @NonNull
+            public Builder setUri(@Nullable Uri uri) {
+                mUri = uri;
+                return this;
+            }
+
+            /**
+             * Constructs a new Item with the properties set on this builder.
+             */
+            @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+            @NonNull
+            public Item build() {
+                return new Item(mText, mHtmlText, mIntent, mPendingIntent, mUri);
+            }
+        }
+
 
         /** @hide */
         public Item(Item other) {
             mText = other.mText;
             mHtmlText = other.mHtmlText;
             mIntent = other.mIntent;
+            mPendingIntent = other.mPendingIntent;
             mUri = other.mUri;
             mActivityInfo = other.mActivityInfo;
             mTextLinks = other.mTextLinks;
@@ -229,10 +315,7 @@
          * Create an Item consisting of a single block of (possibly styled) text.
          */
         public Item(CharSequence text) {
-            mText = text;
-            mHtmlText = null;
-            mIntent = null;
-            mUri = null;
+            this(text, null, null, null, null);
         }
 
         /**
@@ -245,30 +328,21 @@
          * </p>
          */
         public Item(CharSequence text, String htmlText) {
-            mText = text;
-            mHtmlText = htmlText;
-            mIntent = null;
-            mUri = null;
+            this(text, htmlText, null, null, null);
         }
 
         /**
          * Create an Item consisting of an arbitrary Intent.
          */
         public Item(Intent intent) {
-            mText = null;
-            mHtmlText = null;
-            mIntent = intent;
-            mUri = null;
+            this(null, null, intent, null, null);
         }
 
         /**
          * Create an Item consisting of an arbitrary URI.
          */
         public Item(Uri uri) {
-            mText = null;
-            mHtmlText = null;
-            mIntent = null;
-            mUri = uri;
+            this(null, null, null, null, uri);
         }
 
         /**
@@ -276,10 +350,7 @@
          * text, Intent, and/or URI.
          */
         public Item(CharSequence text, Intent intent, Uri uri) {
-            mText = text;
-            mHtmlText = null;
-            mIntent = intent;
-            mUri = uri;
+            this(text, null, intent, null, uri);
         }
 
         /**
@@ -289,6 +360,14 @@
          * will not be done from HTML formatted text into plain text.
          */
         public Item(CharSequence text, String htmlText, Intent intent, Uri uri) {
+            this(text, htmlText, intent, null, uri);
+        }
+
+        /**
+         * Builder ctor.
+         */
+        private Item(CharSequence text, String htmlText, Intent intent, PendingIntent pendingIntent,
+                Uri uri) {
             if (htmlText != null && text == null) {
                 throw new IllegalArgumentException(
                         "Plain text must be supplied if HTML text is supplied");
@@ -296,6 +375,7 @@
             mText = text;
             mHtmlText = htmlText;
             mIntent = intent;
+            mPendingIntent = pendingIntent;
             mUri = uri;
         }
 
@@ -321,6 +401,15 @@
         }
 
         /**
+         * Returns the pending intent in this Item.
+         */
+        @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+        @Nullable
+        public PendingIntent getPendingIntent() {
+            return mPendingIntent;
+        }
+
+        /**
          * Retrieve the raw URI contained in this Item.
          */
         public Uri getUri() {
@@ -777,7 +866,7 @@
             throw new NullPointerException("item is null");
         }
         mIcon = null;
-        mItems = new ArrayList<Item>();
+        mItems = new ArrayList<>();
         mItems.add(item);
         mClipDescription.setIsStyledText(isStyledText());
     }
@@ -794,7 +883,7 @@
             throw new NullPointerException("item is null");
         }
         mIcon = null;
-        mItems = new ArrayList<Item>();
+        mItems = new ArrayList<>();
         mItems.add(item);
         mClipDescription.setIsStyledText(isStyledText());
     }
@@ -826,7 +915,7 @@
     public ClipData(ClipData other) {
         mClipDescription = other.mClipDescription;
         mIcon = other.mIcon;
-        mItems = new ArrayList<Item>(other.mItems);
+        mItems = new ArrayList<>(other.mItems);
     }
 
     /**
@@ -1042,6 +1131,35 @@
     }
 
     /**
+     * Checks if this clip data has a pending intent that is an activity type.
+     * @hide
+     */
+    public boolean hasActivityPendingIntents() {
+        final int size = mItems.size();
+        for (int i = 0; i < size; i++) {
+            final Item item = mItems.get(i);
+            if (item.mPendingIntent != null && item.mPendingIntent.isActivity()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Cleans up all pending intents in the ClipData.
+     * @hide
+     */
+    public void cleanUpPendingIntents() {
+        final int size = mItems.size();
+        for (int i = 0; i < size; i++) {
+            final Item item = mItems.get(i);
+            if (item.mPendingIntent != null) {
+                item.mPendingIntent.cancel();
+            }
+        }
+    }
+
+    /**
      * Prepare this {@link ClipData} to leave an app process.
      *
      * @hide
@@ -1243,6 +1361,7 @@
             TextUtils.writeToParcel(item.mText, dest, flags);
             dest.writeString8(item.mHtmlText);
             dest.writeTypedObject(item.mIntent, flags);
+            dest.writeTypedObject(item.mPendingIntent, flags);
             dest.writeTypedObject(item.mUri, flags);
             dest.writeTypedObject(mParcelItemActivityInfos ? item.mActivityInfo : null, flags);
             dest.writeTypedObject(item.mTextLinks, flags);
@@ -1262,10 +1381,11 @@
             CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
             String htmlText = in.readString8();
             Intent intent = in.readTypedObject(Intent.CREATOR);
+            PendingIntent pendingIntent = in.readTypedObject(PendingIntent.CREATOR);
             Uri uri = in.readTypedObject(Uri.CREATOR);
             ActivityInfo info = in.readTypedObject(ActivityInfo.CREATOR);
             TextLinks textLinks = in.readTypedObject(TextLinks.CREATOR);
-            Item item = new Item(text, htmlText, intent, uri);
+            Item item = new Item(text, htmlText, intent, pendingIntent, uri);
             item.setActivityInfo(info);
             item.setTextLinks(textLinks);
             mItems.add(item);
@@ -1273,7 +1393,7 @@
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ClipData> CREATOR =
-        new Parcelable.Creator<ClipData>() {
+        new Parcelable.Creator<>() {
 
             @Override
             public ClipData createFromParcel(Parcel source) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index b8d7543..c89c735 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6553,6 +6553,15 @@
     public static final String CONTACT_KEYS_SERVICE = "contact_keys";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve an
+     * {@link android.os.ProfilingManager}.
+     *
+     * @see #getSystemService(String)
+     */
+    @FlaggedApi(android.os.Flags.FLAG_TELEMETRY_APIS_FRAMEWORK_INITIALIZATION)
+    public static final String PROFILING_SERVICE = "profiling";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 08871d4..e8031a3 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -19,6 +19,7 @@
 import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_ACTIVITY;
 import static android.content.ContentProvider.maybeAddUserId;
 import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
+import static android.service.chooser.Flags.FLAG_ENABLE_SHARESHEET_METADATA_EXTRA;
 
 import android.Manifest;
 import android.accessibilityservice.AccessibilityService;
@@ -55,6 +56,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.BundleMerger;
+import android.os.CancellationSignal;
 import android.os.IBinder;
 import android.os.IncidentManager;
 import android.os.Parcel;
@@ -72,7 +74,9 @@
 import android.provider.DocumentsProvider;
 import android.provider.MediaStore;
 import android.provider.OpenableColumns;
+import android.service.chooser.AdditionalContentContract;
 import android.service.chooser.ChooserAction;
+import android.service.chooser.ChooserResult;
 import android.telecom.PhoneAccount;
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
@@ -1059,7 +1063,7 @@
         }
 
         if (sender != null) {
-            intent.putExtra(EXTRA_CHOSEN_COMPONENT_INTENT_SENDER, sender);
+            intent.putExtra(EXTRA_CHOOSER_RESULT_INTENT_SENDER, sender);
         }
 
         // Migrate any clip data and flags from target.
@@ -6064,6 +6068,62 @@
     public static final int CHOOSER_CONTENT_TYPE_ALBUM = 1;
 
     /**
+     * Optional argument used to provide a {@link ContentProvider} {@link Uri} to an
+     * {@link #ACTION_CHOOSER} Intent which allows additional toggleable items to be included
+     * in the sharing UI.
+     * <p>
+     * For example, this could be  used to show photos being shared in the context of the user's
+     * entire photo roll, with the option to change the set of photos being shared.
+     * <p>
+     * When this is provided in an {@link #ACTION_CHOOSER} Intent with an {@link #ACTION_SEND} or
+     * {@link #ACTION_SEND_MULTIPLE} target Intent, the sharesheet will query (see
+     * {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)}) this URI to
+     * retrieve a set of additional items available for selection. The set of items returned by the
+     * content provider is expected to contain all the items from the {@link #EXTRA_STREAM}
+     * argument, in their relative order, which will be marked as selected. The URI's authority
+     * must be different from any shared items URI provided in {@link #EXTRA_STREAM} or returned by
+     * the provider.
+     *
+     * <p>The {@link Bundle} argument of the
+     * {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)}
+     * method will contains the original intent Chooser has been launched with under the
+     * {@link #EXTRA_INTENT} key as a context for the current sharing session. The returned
+     * {@link android.database.Cursor} should contain
+     * {@link android.service.chooser.AdditionalContentContract.Columns#URI} column for the item URI
+     * and, optionally, {@link AdditionalContentContract.CursorExtraKeys#POSITION} extra that
+     * specifies the cursor starting position; the item at this position is expected to match the
+     * item specified by {@link #EXTRA_CHOOSER_FOCUSED_ITEM_POSITION}.</p>
+     *
+     * <p>When the user makes a selection change,
+     * {@link ContentProvider#call(String, String, Bundle)} method will be invoked with the "method"
+     * argument set to
+     * {@link android.service.chooser.AdditionalContentContract.MethodNames#ON_SELECTION_CHANGED},
+     * the "arg" argument set to this argument's value, and the "extras" {@link Bundle} argument
+     * containing {@link #EXTRA_INTENT} key containing the original intent Chooser has been launched
+     * with but with the modified target intent --Chooser will modify the target intent according to
+     * the selection changes made by the user.
+     * Applications may implement this method to change any of the following Chooser arguments by
+     * returning new values in the result bundle:
+     * {@link #EXTRA_CHOOSER_TARGETS}, {@link #EXTRA_ALTERNATE_INTENTS},
+     * {@link #EXTRA_CHOOSER_CUSTOM_ACTIONS},
+     * {@link #EXTRA_CHOOSER_MODIFY_SHARE_ACTION},
+     * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER}.</p>
+     */
+    @FlaggedApi(android.service.chooser.Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING)
+    public static final String EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI =
+            "android.intent.extra.CHOOSER_ADDITIONAL_CONTENT_URI";
+
+    /**
+     * Optional argument to be used with {@link #EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI}, used in
+     * combination with {@link #EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI}.
+     * An integer, zero-based index into {@link #EXTRA_STREAM} argument indicating the item that
+     * should be focused by the Chooser in preview.
+     */
+    @FlaggedApi(android.service.chooser.Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING)
+    public static final String EXTRA_CHOOSER_FOCUSED_ITEM_POSITION =
+            "android.intent.extra.CHOOSER_FOCUSED_ITEM_POSITION";
+
+    /**
      * An {@code ArrayList} of {@code String} annotations describing content for
      * {@link #ACTION_CHOOSER}.
      *
@@ -6156,6 +6216,17 @@
     public static final String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS";
 
     /**
+     * A CharSequence of additional text describing the content being shared. This text will be
+     * displayed to the user as a part of the sharesheet when included in an
+     * {@link #ACTION_CHOOSER} {@link Intent}.
+     *
+     * <p>e.g. When sharing a photo, metadata could inform the user that location data is included
+     * in the photo they are sharing.</p>
+     */
+    @FlaggedApi(FLAG_ENABLE_SHARESHEET_METADATA_EXTRA)
+    public static final String EXTRA_METADATA_TEXT = "android.intent.extra.METADATA_TEXT";
+
+    /**
      * A {@link IntentSender} to start after instant app installation success.
      * @hide
      */
@@ -6296,6 +6367,25 @@
             "android.intent.extra.CHOSEN_COMPONENT_INTENT_SENDER";
 
     /**
+     * An {@link IntentSender} that will be notified when a user successfully chooses a target
+     * component or initiates an action such as copy or edit within an {@link #ACTION_CHOOSER}
+     * activity. The IntentSender will have the extra {@link #EXTRA_CHOOSER_RESULT} describing
+     * the result.
+     */
+    @FlaggedApi(android.service.chooser.Flags.FLAG_ENABLE_CHOOSER_RESULT)
+    public static final String EXTRA_CHOOSER_RESULT_INTENT_SENDER =
+            "android.intent.extra.CHOOSER_RESULT_INTENT_SENDER";
+
+    /**
+     * A {@link ChooserResult} which describes how the sharing session completed.
+     * <p>
+     * An instance is supplied to the optional IntentSender provided to
+     * {@link #createChooser(Intent, CharSequence, IntentSender)} when the session completes.
+     */
+    @FlaggedApi(android.service.chooser.Flags.FLAG_ENABLE_CHOOSER_RESULT)
+    public static final String EXTRA_CHOOSER_RESULT = "android.intent.extra.CHOOSER_RESULT";
+
+    /**
      * The {@link ComponentName} chosen by the user to complete an action.
      *
      * @see #EXTRA_CHOSEN_COMPONENT_INTENT_SENDER
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index d913581..e290722 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -677,7 +677,7 @@
      * has at least one HTTP or HTTPS data URI pattern defined, and optionally
      * does not define any non-http/https data URI patterns.
      *
-     * This will check if if the Intent action is {@link android.content.Intent#ACTION_VIEW} and
+     * This will check if the Intent action is {@link android.content.Intent#ACTION_VIEW} and
      * the Intent category is {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent
      * data scheme is "http" or "https".
      *
@@ -718,7 +718,7 @@
         }
 
         // We get here if:
-        //   1) onlyWebSchemes and no non-web schemes were found, i.e success; or
+        //   1) onlyWebSchemes and no non-web schemes were found, i.e. success; or
         //   2) !onlyWebSchemes and no http/https schemes were found, i.e. failure.
         return onlyWebSchemes;
     }
@@ -728,7 +728,7 @@
      *
      * @return True if the filter needs to be automatically verified. False otherwise.
      *
-     * This will check if if the Intent action is {@link android.content.Intent#ACTION_VIEW} and
+     * This will check if the Intent action is {@link android.content.Intent#ACTION_VIEW} and
      * the Intent category is {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent
      * data scheme is "http" or "https".
      *
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 407ffbb..49c8a7c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -16,6 +16,8 @@
 
 package android.content.pm;
 
+import static android.media.audio.Flags.FLAG_FEATURE_SPATIAL_AUDIO_HEADTRACKING_LOW_LATENCY;
+
 import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES;
 
 import android.Manifest;
@@ -3065,6 +3067,17 @@
     public static final String FEATURE_AUDIO_PRO = "android.hardware.audio.pro";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}
+     * which indicates whether head tracking for spatial audio operates with low-latency,
+     * as defined by the CDD criteria for the feature.
+     *
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    @FlaggedApi(FLAG_FEATURE_SPATIAL_AUDIO_HEADTRACKING_LOW_LATENCY)
+    public static final String FEATURE_AUDIO_SPATIAL_HEADTRACKING_LOW_LATENCY =
+            "android.hardware.audio.spatial.headtracking.low_latency";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device is capable of communicating with
      * other devices via Bluetooth.
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 2b378b1..5b0cee7 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -142,6 +142,28 @@
      * the {@link android.R.attr#foregroundServiceType} attribute.
      * Data(photo, file, account) upload/download, backup/restore, import/export, fetch,
      * transfer over network between device and cloud.
+     *
+     * <p>This type has time limit of 6 hours starting from Android version
+     * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}.
+     * A foreground service of this type must be stopped within the timeout by
+     * {@link android.app.Service#stopSelf()},
+     * {@link android.content.Context#stopService(android.content.Intent)} or their overloads).
+     * {@link android.app.Service#stopForeground(int)} will also work, which will demote the
+     * service to a "background" service, which will soon be stopped by the system.
+     *
+     * <p>If the service isn't stopped within the timeout,
+     * {@link android.app.Service#onTimeout(int, int)} will be called.
+     *
+     * <p>Also note, even though
+     * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_DATA_SYNC} can be used on
+     * Android versions prior to {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, since
+     * {@link android.app.Service#onTimeout(int, int)} did not exist on such versions, it will
+     * never be called.
+     *
+     * Because of this, developers must make sure to stop the foreground service even if
+     * {@link android.app.Service#onTimeout(int, int)} is not called on such versions.
+     *
+     * @see android.app.Service#onTimeout(int, int)
      */
     @RequiresPermission(
             value = Manifest.permission.FOREGROUND_SERVICE_DATA_SYNC,
@@ -483,6 +505,27 @@
      * 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.
+     *
+     * This type has time limit of 6 hours.
+     * A foreground service of this type must be stopped within the timeout by
+     * {@link android.app.Service#stopSelf()},
+     * {@link android.content.Context#stopService(android.content.Intent)} or their overloads).
+     * {@link android.app.Service#stopForeground(int)} will also work, which will demote the
+     * service to a "background" service, which will soon be stopped by the system.
+     *
+     * <p>If the service isn't stopped within the timeout,
+     * {@link android.app.Service#onTimeout(int, int)} will be called.
+     *
+     * <p>Also note, even though
+     * {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING} was added in
+     * Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, it can be also used
+     * on prior android versions (just like other new foreground service types can be used).
+     * However, because {@link android.app.Service#onTimeout(int, int)} did not exist on prior
+     * versions, it will never be called on such versions.
+     * Because of this, developers must make sure to stop the foreground service even if
+     * {@link android.app.Service#onTimeout(int, int)} is not called on such versions.
+     *
+     * @see android.app.Service#onTimeout(int, int)
      */
     @RequiresPermission(
             value = Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING
diff --git a/core/java/android/content/pm/overlay/OverlayPaths.java b/core/java/android/content/pm/overlay/OverlayPaths.java
index a4db733..bd74b0b 100644
--- a/core/java/android/content/pm/overlay/OverlayPaths.java
+++ b/core/java/android/content/pm/overlay/OverlayPaths.java
@@ -49,6 +49,13 @@
     public static class Builder {
         final OverlayPaths mPaths = new OverlayPaths();
 
+        public Builder() {}
+
+        public Builder(@NonNull OverlayPaths base) {
+            mPaths.mResourceDirs.addAll(base.getResourceDirs());
+            mPaths.mOverlayPaths.addAll(base.getOverlayPaths());
+        }
+
         /**
          * Adds a non-APK path to the contents of {@link OverlayPaths#getOverlayPaths()}.
          */
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index bdaf9d7..d4c58b2 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -200,6 +200,25 @@
             return this;
         }
 
+        /**
+         * Optional: Sets logo description text that will be shown on the prompt.
+         *
+         * <p> Note that using this method is not recommended in most scenarios because the calling
+         * application's name will be used by default. Setting the logo description is intended for
+         * large bundled applications that perform a wide range of functions and need to show
+         * distinct description for each function.
+         *
+         * @param logoDescription The logo description text that will be shown on the prompt.
+         * @return This builder.
+         */
+        @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+        @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
+        @NonNull
+        public BiometricPrompt.Builder setLogoDescription(@NonNull String logoDescription) {
+            mPromptInfo.setLogoDescription(logoDescription);
+            return this;
+        }
+
 
         /**
          * Required: Sets the title that will be shown on the prompt.
@@ -743,7 +762,20 @@
         return mPromptInfo.getLogoBitmap();
     }
 
-
+    /**
+     * Gets the logo description for the prompt, as set by
+     * {@link Builder#setDescription(CharSequence)}.
+     * Currently for system applications use only.
+     *
+     * @return The logo description of the prompt, or null if the prompt has no logo description
+     * set.
+     */
+    @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+    @RequiresPermission(SET_BIOMETRIC_DIALOG_LOGO)
+    @Nullable
+    public String getLogoDescription() {
+        return mPromptInfo.getLogoDescription();
+    }
 
     /**
      * Gets the title for the prompt, as set by {@link Builder#setTitle(CharSequence)}.
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index 0f9cadc..2236660 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -34,6 +34,7 @@
 
     @DrawableRes private int mLogoRes = -1;
     @Nullable private Bitmap mLogoBitmap;
+    @Nullable private String mLogoDescription;
     @NonNull private CharSequence mTitle;
     private boolean mUseDefaultTitle;
     @Nullable private CharSequence mSubtitle;
@@ -62,6 +63,7 @@
     PromptInfo(Parcel in) {
         mLogoRes = in.readInt();
         mLogoBitmap = in.readTypedObject(Bitmap.CREATOR);
+        mLogoDescription = in.readString();
         mTitle = in.readCharSequence();
         mUseDefaultTitle = in.readBoolean();
         mSubtitle = in.readCharSequence();
@@ -106,6 +108,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mLogoRes);
         dest.writeTypedObject(mLogoBitmap, 0);
+        dest.writeString(mLogoDescription);
         dest.writeCharSequence(mTitle);
         dest.writeBoolean(mUseDefaultTitle);
         dest.writeCharSequence(mSubtitle);
@@ -173,6 +176,8 @@
             return true;
         } else if (mLogoBitmap != null) {
             return true;
+        } else if (mLogoDescription != null) {
+            return true;
         }
         return false;
     }
@@ -189,6 +194,10 @@
         checkOnlyOneLogoSet();
     }
 
+    public void setLogoDescription(@NonNull String logoDescription) {
+        mLogoDescription = logoDescription;
+    }
+
     public void setTitle(CharSequence title) {
         mTitle = title;
     }
@@ -282,6 +291,10 @@
         return mLogoBitmap;
     }
 
+    public String getLogoDescription() {
+        return mLogoDescription;
+    }
+
     public CharSequence getTitle() {
         return mTitle;
     }
diff --git a/services/core/java/com/android/server/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
similarity index 96%
rename from services/core/java/com/android/server/devicestate/DeviceState.java
rename to core/java/android/hardware/devicestate/DeviceState.java
index 2ba59b0..5a34905 100644
--- a/services/core/java/com/android/server/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.devicestate;
+package android.hardware.devicestate;
 
 import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
@@ -22,7 +22,6 @@
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
-import android.hardware.devicestate.DeviceStateManager;
 
 import com.android.internal.util.Preconditions;
 
@@ -38,9 +37,9 @@
  * state of the system. This is useful for variable-state devices, like foldable or rollable
  * devices, that can be configured by users into differing hardware states, which each may have a
  * different expected use case.
+ * @hide
  *
- * @see DeviceStateProvider
- * @see DeviceStateManagerService
+ * @see DeviceStateManager
  */
 public final class DeviceState {
     /**
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index 4a5c4c8..8616b6b 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -16,13 +16,11 @@
 
 package android.hardware.usb;
 
-import android.Manifest;
 import android.annotation.CheckResult;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.hardware.usb.flags.Flags;
 import android.os.Parcel;
@@ -30,7 +28,6 @@
 
 import com.android.internal.annotations.Immutable;
 
-import java.lang.StringBuilder;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -580,6 +577,21 @@
     }
 
     /**
+     * This function checks if the port is USB Power Delivery (PD) compliant -
+     * https://www.usb.org/usb-charger-pd. All of the power and data roles must be supported for a
+     * port to be PD compliant.
+     *
+     * @return true if the port is PD compliant.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_IS_PD_COMPLIANT_API)
+    public boolean isPdCompliant() {
+        return isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_DEVICE)
+                && isRoleCombinationSupported(POWER_ROLE_SINK, DATA_ROLE_HOST)
+                && isRoleCombinationSupported(POWER_ROLE_SOURCE, DATA_ROLE_DEVICE)
+                && isRoleCombinationSupported(POWER_ROLE_SOURCE, DATA_ROLE_HOST);
+    }
+
+    /**
      * Get the supported role combinations.
      */
     public int getSupportedRoleCombinations() {
diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
new file mode 100644
index 0000000..cc56a31
--- /dev/null
+++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.hardware.usb.flags"
+
+flag {
+    name: "enable_is_pd_compliant_api"
+    namespace: "usb"
+    description: "Feature flag for the api to check if a port is PD compliant"
+    bug: "323470419"
+}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index a6b2ed0..e090942 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1899,7 +1899,7 @@
         public short batteryTemperature;
         // Battery voltage in millivolts (mV).
         @UnsupportedAppUsage
-        public char batteryVoltage;
+        public short batteryVoltage;
 
         // The charge of the battery in micro-Ampere-hours.
         public int batteryChargeUah;
@@ -2161,7 +2161,7 @@
             batteryPlugType = (byte)((bat>>24)&0xf);
             int bat2 = src.readInt();
             batteryTemperature = (short)(bat2&0xffff);
-            batteryVoltage = (char)((bat2>>16)&0xffff);
+            batteryVoltage = (short) ((bat2 >> 16) & 0xffff);
             batteryChargeUah = src.readInt();
             modemRailChargeMah = src.readDouble();
             wifiRailChargeMah = src.readDouble();
diff --git a/core/java/android/os/ProfilingServiceManager.java b/core/java/android/os/ProfilingServiceManager.java
new file mode 100644
index 0000000..cc77f5b
--- /dev/null
+++ b/core/java/android/os/ProfilingServiceManager.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
+
+/**
+ * Provides a way to register and obtain the system service binder objects managed by the profiling
+ * service.
+ *
+ * <p> Only the profiling mainline module will be able to access an instance of this class.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_TELEMETRY_APIS_FRAMEWORK_INITIALIZATION)
+@SystemApi(client = Client.MODULE_LIBRARIES)
+public class ProfilingServiceManager {
+
+    /** @hide */
+    public ProfilingServiceManager() {}
+
+    /**
+     * A class that exposes the methods to register and obtain each system service.
+     */
+    public static final class ServiceRegisterer {
+        private final String mServiceName;
+
+        /** @hide */
+        public ServiceRegisterer(String serviceName) {
+            mServiceName = serviceName;
+        }
+
+        /**
+         * Get the system server binding object for ProfilingService.
+         *
+         * <p> This blocks until the service instance is ready.
+         * or a timeout happens, in which case it returns null.
+         */
+        @Nullable
+        public IBinder get() {
+            return ServiceManager.getService(mServiceName);
+        }
+
+        /**
+         * Get the system server binding object for a service.
+         *
+         * <p>This blocks until the service instance is ready,
+         * or a timeout happens, in which case it throws {@link ServiceNotFoundException}.
+         */
+        @Nullable
+        public IBinder getOrThrow() throws ServiceNotFoundException {
+            try {
+                return ServiceManager.getServiceOrThrow(mServiceName);
+            } catch (ServiceManager.ServiceNotFoundException e) {
+                throw new ServiceNotFoundException(mServiceName);
+            }
+        }
+    }
+
+    /**
+     * See {@link ServiceRegisterer#getOrThrow()}
+     */
+    public static class ServiceNotFoundException extends ServiceManager.ServiceNotFoundException {
+        /**
+         * Constructor
+         *
+         * @param name the name of the binder service that cannot be found.
+         */
+        public ServiceNotFoundException(@NonNull String name) {
+            super(name);
+        }
+    }
+
+    /**
+     * Returns {@link ServiceRegisterer} for the "profiling" service.
+     */
+    @NonNull
+    public ServiceRegisterer getProfilingServiceRegisterer() {
+        return new ServiceRegisterer("profiling_service");
+    }
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 0fbdbc4..de32423 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -180,11 +180,14 @@
 
 
     /**
-     * User type representing a private profile.
+     * User type representing a private profile. Private profile is a user profile that can be used
+     * as an alternative user-space to install and use sensitive apps.
+     * UI surfaces can adopt an alternative strategy to show apps belonging to this profile, in line
+     * with their sensitive nature.
      * @hide
      */
     @FlaggedApi(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE)
-    @TestApi
+    @SystemApi
     public static final String USER_TYPE_PROFILE_PRIVATE = "android.os.usertype.profile.PRIVATE";
 
     /**
@@ -1424,8 +1427,8 @@
     public static final String DISALLOW_RECORD_AUDIO = "no_record_audio";
 
     /**
-     * Specifies if a user is not allowed to run in the background and should be stopped during
-     * user switch. The default value is <code>false</code>.
+     * Specifies if a user is not allowed to run in the background and should be stopped and locked
+     * during user switch. The default value is <code>false</code>.
      *
      * <p>This restriction can be set by device owners and profile owners.
      *
diff --git a/core/java/android/os/storage/OWNERS b/core/java/android/os/storage/OWNERS
index 6941857..2cb16d337b 100644
--- a/core/java/android/os/storage/OWNERS
+++ b/core/java/android/os/storage/OWNERS
@@ -10,7 +10,6 @@
 krishang@google.com
 riyaghai@google.com
 sahanas@google.com
-sergeynv@google.com
 shikhamalhotra@google.com
 shubhisaxena@google.com
 tylersaunders@google.com
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 8c70501..9d7fb70 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -94,3 +94,11 @@
   description: "Enable signature permission allowlist"
   bug: "308573169"
 }
+
+flag {
+    name: "device_aware_permissions_enabled"
+    is_fixed_read_only: true
+    namespace: "permissions"
+    description: "When the flag is off no permissions can be device aware"
+    bug: "274852670"
+}
\ No newline at end of file
diff --git a/core/java/android/provider/BlockedNumberContract.java b/core/java/android/provider/BlockedNumberContract.java
index 5d00b29..4075e90 100644
--- a/core/java/android/provider/BlockedNumberContract.java
+++ b/core/java/android/provider/BlockedNumberContract.java
@@ -15,12 +15,20 @@
  */
 package android.provider;
 
+import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
 import android.annotation.WorkerThread;
 import android.content.Context;
 import android.net.Uri;
 import android.os.Bundle;
 import android.telecom.Log;
+import android.telecom.TelecomManager;
+
+import com.android.server.telecom.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -214,6 +222,333 @@
          * <p>TYPE: String</p>
          */
         public static final String COLUMN_E164_NUMBER = "e164_number";
+
+        /**
+         * A protected broadcast intent action for letting components with
+         * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} know that the block suppression
+         * status as returned by {@link #getBlockSuppressionStatus(Context)} has been updated.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static final String ACTION_BLOCK_SUPPRESSION_STATE_CHANGED =
+                "android.provider.action.BLOCK_SUPPRESSION_STATE_CHANGED";
+
+        /**
+         * Preference key of block numbers not in contacts setting.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static final String ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED =
+                "block_numbers_not_in_contacts_setting";
+
+        /**
+         * Preference key of block private number calls setting.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static final String ENHANCED_SETTING_KEY_BLOCK_PRIVATE =
+                "block_private_number_calls_setting";
+
+        /**
+         * Preference key of block payphone calls setting.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static final String ENHANCED_SETTING_KEY_BLOCK_PAYPHONE =
+                "block_payphone_calls_setting";
+
+        /**
+         * Preference key of block unknown calls setting.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static final String ENHANCED_SETTING_KEY_BLOCK_UNKNOWN =
+                "block_unknown_calls_setting";
+
+        /**
+         * Preference key for whether should show an emergency call notification.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static final String ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION =
+                "show_emergency_call_notification";
+
+        /**
+         * Preference key of block unavailable calls setting.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static final String ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE =
+                "block_unavailable_calls_setting";
+
+        /**
+         * Notifies the provider that emergency services were contacted by the user.
+         * <p> This results in {@link #shouldSystemBlockNumber} returning {@code false} independent
+         * of the contents of the provider for a duration defined by
+         * {@link android.telephony.CarrierConfigManager#KEY_DURATION_BLOCKING_DISABLED_AFTER_EMERGENCY_INT}
+         * the provider unless {@link #endBlockSuppression(Context)} is called.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(allOf = {
+                android.Manifest.permission.READ_BLOCKED_NUMBERS,
+                android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+        })
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static void notifyEmergencyContact(@NonNull Context context) {
+            verifyBlockedNumbersPermission(context);
+            try {
+                Log.i(LOG_TAG, "notifyEmergencyContact; caller=%s", context.getOpPackageName());
+                context.getContentResolver().call(
+                        AUTHORITY_URI, SystemContract.METHOD_NOTIFY_EMERGENCY_CONTACT, null, null);
+            } catch (NullPointerException | IllegalArgumentException ex) {
+                // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
+                // either of these happen.
+                Log.w(null, "notifyEmergencyContact: provider not ready.");
+            }
+        }
+
+        /**
+         * Notifies the provider to disable suppressing blocking. If emergency services were not
+         * contacted recently at all, calling this method is a no-op.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(allOf = {
+                android.Manifest.permission.READ_BLOCKED_NUMBERS,
+                android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+        })
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static void endBlockSuppression(@NonNull Context context) {
+            verifyBlockedNumbersPermission(context);
+            String caller = context.getOpPackageName();
+            Log.i(LOG_TAG, "endBlockSuppression: caller=%s", caller);
+            context.getContentResolver().call(
+                    AUTHORITY_URI, SystemContract.METHOD_END_BLOCK_SUPPRESSION, null, null);
+        }
+
+        /**
+         * Returns {@code true} if {@code phoneNumber} is blocked taking
+         * {@link #notifyEmergencyContact(Context)} into consideration. If emergency services
+         * have not been contacted recently and enhanced call blocking not been enabled, this
+         * method is equivalent to {@link #isBlocked(Context, String)}.
+         *
+         * @param context the context of the caller.
+         * @param phoneNumber the number to check.
+         * @param numberPresentation the presentation code associated with the call.
+         * @param isNumberInContacts indicates if the provided number exists as a contact.
+         * @return result code indicating if the number should be blocked, and if so why.
+         *         Valid values are: {@link #STATUS_NOT_BLOCKED}, {@link #STATUS_BLOCKED_IN_LIST},
+         *         {@link #STATUS_BLOCKED_NOT_IN_CONTACTS}, {@link #STATUS_BLOCKED_PAYPHONE},
+         *         {@link #STATUS_BLOCKED_RESTRICTED}, {@link #STATUS_BLOCKED_UNKNOWN_NUMBER}.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(allOf = {
+                android.Manifest.permission.READ_BLOCKED_NUMBERS,
+                android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+        })
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static int shouldSystemBlockNumber(@NonNull Context context,
+                @NonNull String phoneNumber, @TelecomManager.Presentation int numberPresentation,
+                boolean isNumberInContacts) {
+            verifyBlockedNumbersPermission(context);
+            try {
+                String caller = context.getOpPackageName();
+                Bundle extras = new Bundle();
+                extras.putInt(BlockedNumberContract.EXTRA_CALL_PRESENTATION, numberPresentation);
+                extras.putBoolean(BlockedNumberContract.EXTRA_CONTACT_EXIST, isNumberInContacts);
+                final Bundle res = context.getContentResolver().call(AUTHORITY_URI,
+                        SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER, phoneNumber, extras);
+                int blockResult = res != null ? res.getInt(RES_BLOCK_STATUS, STATUS_NOT_BLOCKED) :
+                        BlockedNumberContract.STATUS_NOT_BLOCKED;
+                Log.d(LOG_TAG, "shouldSystemBlockNumber: number=%s, caller=%s, result=%s",
+                        Log.piiHandle(phoneNumber), caller,
+                        SystemContract.blockStatusToString(blockResult));
+                return blockResult;
+            } catch (NullPointerException | IllegalArgumentException ex) {
+                // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
+                // either of these happen.
+                Log.w(null, "shouldSystemBlockNumber: provider not ready.");
+                return BlockedNumberContract.STATUS_NOT_BLOCKED;
+            }
+        }
+
+        /**
+         * Returns the current status of block suppression.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(allOf = {
+                android.Manifest.permission.READ_BLOCKED_NUMBERS,
+                android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+        })
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static @NonNull BlockSuppressionStatus getBlockSuppressionStatus(
+                @NonNull Context context) {
+            verifyBlockedNumbersPermission(context);
+            final Bundle res = context.getContentResolver().call(
+                    AUTHORITY_URI, SystemContract.METHOD_GET_BLOCK_SUPPRESSION_STATUS, null, null);
+            BlockSuppressionStatus blockSuppressionStatus = new BlockSuppressionStatus(
+                    res.getBoolean(SystemContract.RES_IS_BLOCKING_SUPPRESSED, false),
+                    res.getLong(SystemContract.RES_BLOCKING_SUPPRESSED_UNTIL_TIMESTAMP, 0));
+            Log.d(LOG_TAG, "getBlockSuppressionStatus: caller=%s, status=%s",
+                    context.getOpPackageName(), blockSuppressionStatus);
+            return blockSuppressionStatus;
+        }
+
+        /**
+         * Check whether should show the emergency call notification.
+         *
+         * @param context the context of the caller.
+         * @return {@code true} if should show emergency call notification. {@code false} otherwise.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(allOf = {
+                android.Manifest.permission.READ_BLOCKED_NUMBERS,
+                android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+        })
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static boolean shouldShowEmergencyCallNotification(@NonNull Context context) {
+            verifyBlockedNumbersPermission(context);
+            try {
+                final Bundle res = context.getContentResolver().call(AUTHORITY_URI,
+                        SystemContract.METHOD_SHOULD_SHOW_EMERGENCY_CALL_NOTIFICATION, null, null);
+                return res != null && res.getBoolean(RES_SHOW_EMERGENCY_CALL_NOTIFICATION, false);
+            } catch (NullPointerException | IllegalArgumentException ex) {
+                // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
+                // either of these happen.
+                Log.w(null, "shouldShowEmergencyCallNotification: provider not ready.");
+                return false;
+            }
+        }
+
+        /**
+         * Check whether the enhanced block setting is enabled.
+         *
+         * @param context the context of the caller.
+         * @param key the key of the setting to check, can be
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION}
+         * @return {@code true} if the setting is enabled. {@code false} otherwise.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(allOf = {
+                android.Manifest.permission.READ_BLOCKED_NUMBERS,
+                android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+        })
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static boolean getBlockedNumberSetting(
+                @NonNull Context context, @NonNull String key) {
+            verifyBlockedNumbersPermission(context);
+            Bundle extras = new Bundle();
+            extras.putString(EXTRA_ENHANCED_SETTING_KEY, key);
+            try {
+                final Bundle res = context.getContentResolver().call(AUTHORITY_URI,
+                        SystemContract.METHOD_GET_ENHANCED_BLOCK_SETTING, null, extras);
+                return res != null && res.getBoolean(RES_ENHANCED_SETTING_IS_ENABLED, false);
+            } catch (NullPointerException | IllegalArgumentException ex) {
+                // The content resolver can throw an NPE or IAE; we don't want to crash Telecom if
+                // either of these happen.
+                Log.w(null, "getEnhancedBlockSetting: provider not ready.");
+                return false;
+            }
+        }
+
+        /**
+         * Set the enhanced block setting enabled status.
+         *
+         * @param context the context of the caller.
+         * @param key the key of the setting to set, can be
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE}
+         *        {@link SystemContract#ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION}
+         * @param value the enabled statue of the setting to set.
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(allOf = {
+                android.Manifest.permission.READ_BLOCKED_NUMBERS,
+                android.Manifest.permission.WRITE_BLOCKED_NUMBERS
+        })
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static void setBlockedNumberSetting(@NonNull Context context,
+                @NonNull String key, boolean value) {
+            verifyBlockedNumbersPermission(context);
+            Bundle extras = new Bundle();
+            extras.putString(EXTRA_ENHANCED_SETTING_KEY, key);
+            extras.putBoolean(EXTRA_ENHANCED_SETTING_VALUE, value);
+            context.getContentResolver().call(AUTHORITY_URI,
+                    SystemContract.METHOD_SET_ENHANCED_BLOCK_SETTING, null, extras);
+        }
+
+        /**
+         * Represents the current status of
+         * {@link #shouldSystemBlockNumber(Context, String, int, boolean)}. If emergency services
+         * have been contacted recently, {@link #mIsSuppressed} is {@code true}, and blocking
+         * is disabled until the timestamp {@link #mUntilTimestampMillis}.
+         * @hide
+         */
+        @SystemApi
+        @FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
+        public static final class BlockSuppressionStatus {
+            private boolean mIsSuppressed;
+
+            /**
+             * Timestamp in milliseconds from epoch.
+             */
+            private long mUntilTimestampMillis;
+
+            public BlockSuppressionStatus(boolean isSuppressed, long untilTimestampMillis) {
+                this.mIsSuppressed = isSuppressed;
+                this.mUntilTimestampMillis = untilTimestampMillis;
+            }
+
+            @Override
+            public String toString() {
+                return "[BlockSuppressionStatus; isSuppressed=" + mIsSuppressed + ", until="
+                        + mUntilTimestampMillis + "]";
+            }
+
+            public boolean getIsSuppressed() {
+                return mIsSuppressed;
+            }
+
+            public long getUntilTimestampMillis() {
+                return mUntilTimestampMillis;
+            }
+        }
+
+        /**
+         * Verifies that the caller holds both the
+         * {@link android.Manifest.permission#READ_BLOCKED_NUMBERS} permission and the
+         * {@link android.Manifest.permission#WRITE_BLOCKED_NUMBERS} permission.
+         *
+         * @param context
+         * @throws SecurityException if the caller is missing the necessary permissions
+         */
+        private static void verifyBlockedNumbersPermission(Context context) {
+            context.enforceCallingOrSelfPermission(Manifest.permission.READ_BLOCKED_NUMBERS,
+                    "Caller does not have the android.permission.READ_BLOCKED_NUMBERS permission");
+            context.enforceCallingOrSelfPermission(Manifest.permission.WRITE_BLOCKED_NUMBERS,
+                    "Caller does not have the android.permission.WRITE_BLOCKED_NUMBERS permission");
+        }
     }
 
     /** @hide */
@@ -558,7 +893,7 @@
          *        {@link #ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
          *        {@link #ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
          *        {@link #ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE}
-         *        {@link #ENHANCED_SETTING_KEY_EMERGENCY_CALL_NOTIFICATION_SHOWING}
+         *        {@link #ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION}
          * @return {@code true} if the setting is enabled. {@code false} otherwise.
          */
         public static boolean getEnhancedBlockSetting(Context context, String key) {
@@ -586,7 +921,7 @@
          *        {@link #ENHANCED_SETTING_KEY_BLOCK_PAYPHONE}
          *        {@link #ENHANCED_SETTING_KEY_BLOCK_UNKNOWN}
          *        {@link #ENHANCED_SETTING_KEY_BLOCK_UNAVAILABLE}
-         *        {@link #ENHANCED_SETTING_KEY_EMERGENCY_CALL_NOTIFICATION_SHOWING}
+         *        {@link #ENHANCED_SETTING_KEY_SHOW_EMERGENCY_CALL_NOTIFICATION}
          * @param value the enabled statue of the setting to set.
          */
         public static void setEnhancedBlockSetting(Context context, String key, boolean value) {
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 7d127ad..c13dd36 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -19,6 +19,7 @@
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.LongDef;
 import android.annotation.NonNull;
@@ -55,6 +56,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.server.telecom.flags.Flags;
+
 import java.io.ByteArrayOutputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -427,6 +430,8 @@
             private double mLongitude = Double.NaN;
             private Uri mPictureUri;
             private int mIsPhoneAccountMigrationPending;
+            private boolean mIsBusinessCall;
+            private String mBusinessName;
 
             /**
              * @param callerInfo the CallerInfo object to get the target contact from.
@@ -645,15 +650,44 @@
             }
 
             /**
+             * @param isBusinessCall should be set if the caller is a business call
+             */
+            @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
+            public @NonNull AddCallParametersBuilder setIsBusinessCall(boolean isBusinessCall) {
+                mIsBusinessCall = isBusinessCall;
+                return this;
+            }
+
+            /**
+             * @param businessName should be set if the caller is a business call
+             */
+            @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
+            public @NonNull AddCallParametersBuilder setBusinessName(String businessName) {
+                mBusinessName = businessName;
+                return this;
+            }
+
+            /**
              * Builds the object
              */
             public @NonNull AddCallParams build() {
-                return new AddCallParams(mCallerInfo, mNumber, mPostDialDigits, mViaNumber,
-                        mPresentation, mCallType, mFeatures, mAccountHandle, mStart, mDuration,
-                        mDataUsage, mAddForAllUsers, mUserToBeInsertedTo, mIsRead, mCallBlockReason,
-                        mCallScreeningAppName, mCallScreeningComponentName, mMissedReason,
-                        mPriority, mSubject, mLatitude, mLongitude, mPictureUri,
-                        mIsPhoneAccountMigrationPending);
+                if (Flags.businessCallComposer()) {
+                    return new AddCallParams(mCallerInfo, mNumber, mPostDialDigits, mViaNumber,
+                            mPresentation, mCallType, mFeatures, mAccountHandle, mStart, mDuration,
+                            mDataUsage, mAddForAllUsers, mUserToBeInsertedTo, mIsRead,
+                            mCallBlockReason,
+                            mCallScreeningAppName, mCallScreeningComponentName, mMissedReason,
+                            mPriority, mSubject, mLatitude, mLongitude, mPictureUri,
+                            mIsPhoneAccountMigrationPending, mIsBusinessCall, mBusinessName);
+                } else {
+                    return new AddCallParams(mCallerInfo, mNumber, mPostDialDigits, mViaNumber,
+                            mPresentation, mCallType, mFeatures, mAccountHandle, mStart, mDuration,
+                            mDataUsage, mAddForAllUsers, mUserToBeInsertedTo, mIsRead,
+                            mCallBlockReason,
+                            mCallScreeningAppName, mCallScreeningComponentName, mMissedReason,
+                            mPriority, mSubject, mLatitude, mLongitude, mPictureUri,
+                            mIsPhoneAccountMigrationPending);
+                }
             }
         }
 
@@ -681,6 +715,8 @@
         private double mLongitude = Double.NaN;
         private Uri mPictureUri;
         private int mIsPhoneAccountMigrationPending;
+        private boolean mIsBusinessCall;
+        private String mBusinessName;
 
         private AddCallParams(CallerInfo callerInfo, String number, String postDialDigits,
                 String viaNumber, int presentation, int callType, int features,
@@ -717,6 +753,43 @@
             mIsPhoneAccountMigrationPending = isPhoneAccountMigrationPending;
         }
 
+        private AddCallParams(CallerInfo callerInfo, String number, String postDialDigits,
+                String viaNumber, int presentation, int callType, int features,
+                PhoneAccountHandle accountHandle, long start, int duration, long dataUsage,
+                boolean addForAllUsers, UserHandle userToBeInsertedTo, boolean isRead,
+                int callBlockReason,
+                CharSequence callScreeningAppName, String callScreeningComponentName,
+                long missedReason,
+                int priority, String subject, double latitude, double longitude, Uri pictureUri,
+                int isPhoneAccountMigrationPending, boolean isBusinessCall, String businessName) {
+            mCallerInfo = callerInfo;
+            mNumber = number;
+            mPostDialDigits = postDialDigits;
+            mViaNumber = viaNumber;
+            mPresentation = presentation;
+            mCallType = callType;
+            mFeatures = features;
+            mAccountHandle = accountHandle;
+            mStart = start;
+            mDuration = duration;
+            mDataUsage = dataUsage;
+            mAddForAllUsers = addForAllUsers;
+            mUserToBeInsertedTo = userToBeInsertedTo;
+            mIsRead = isRead;
+            mCallBlockReason = callBlockReason;
+            mCallScreeningAppName = callScreeningAppName;
+            mCallScreeningComponentName = callScreeningComponentName;
+            mMissedReason = missedReason;
+            mPriority = priority;
+            mSubject = subject;
+            mLatitude = latitude;
+            mLongitude = longitude;
+            mPictureUri = pictureUri;
+            mIsPhoneAccountMigrationPending = isPhoneAccountMigrationPending;
+            mIsBusinessCall = isBusinessCall;
+            mBusinessName = businessName;
+        }
+
     }
 
     /**
@@ -915,6 +988,19 @@
          */
         public static final String NUMBER = "number";
 
+
+        /**
+         * Boolean indicating whether the call is a business call.
+         */
+        @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
+        public static final String IS_BUSINESS_CALL = "is_business_call";
+
+        /**
+         * String that stores the asserted display name associated with business call.
+         */
+        @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
+        public static final String ASSERTED_DISPLAY_NAME = "asserted_display_name";
+
         /**
          * The number presenting rules set by the network.
          *
@@ -1713,7 +1799,6 @@
             }
 
             ContentValues values = new ContentValues(14);
-
             values.put(NUMBER, params.mNumber);
             values.put(POST_DIAL_DIGITS, params.mPostDialDigits);
             values.put(VIA_NUMBER, params.mViaNumber);
@@ -1746,7 +1831,10 @@
                 values.put(COMPOSER_PHOTO_URI, params.mPictureUri.toString());
             }
             values.put(IS_PHONE_ACCOUNT_MIGRATION_PENDING, params.mIsPhoneAccountMigrationPending);
-
+            if (Flags.businessCallComposer()) {
+                values.put(IS_BUSINESS_CALL, Integer.valueOf(params.mIsBusinessCall ? 1 : 0));
+                values.put(ASSERTED_DISPLAY_NAME, params.mBusinessName);
+            }
             if ((params.mCallerInfo != null) && (params.mCallerInfo.getContactId() > 0)) {
                 // Update usage information for the number associated with the contact ID.
                 // We need to use both the number and the ID for obtaining a data ID since other
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b026ce9..0db68b4 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12286,6 +12286,14 @@
                 "extra_automatic_power_save_mode";
 
         /**
+         * Whether contextual screen timeout is enabled.
+         *
+         * @hide
+         */
+        public static final String CONTEXTUAL_SCREEN_TIMEOUT_ENABLED =
+                "contextual_screen_timeout_enabled";
+
+        /**
          * Whether lockscreen weather is enabled.
          *
          * @hide
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 658cec8..88bd87e 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4956,6 +4956,26 @@
          */
         public static final String COLUMN_SERVICE_CAPABILITIES = "service_capabilities";
 
+        /**
+         * TelephonyProvider column name for satellite entitlement status. The value of this column
+         * is set based on entitlement query result for satellite configuration.
+         * By default, it's disabled.
+         *
+         * @hide
+         */
+        public static final String COLUMN_SATELLITE_ENTITLEMENT_STATUS =
+                "satellite_entitlement_status";
+
+        /**
+         * TelephonyProvider column name for satellite entitlement plmns. The value of this
+         * column is set based on entitlement query result for satellite configuration.
+         * By default, it's empty.
+         *
+         * @hide
+         */
+        public static final String COLUMN_SATELLITE_ENTITLEMENT_PLMNS =
+                "satellite_entitlement_plmns";
+
         /** All columns in {@link SimInfo} table. */
         private static final List<String> ALL_COLUMNS = List.of(
                 COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
@@ -5029,7 +5049,9 @@
                 COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
                 COLUMN_IS_NTN,
                 COLUMN_SERVICE_CAPABILITIES,
-                COLUMN_TRANSFER_STATUS
+                COLUMN_TRANSFER_STATUS,
+                COLUMN_SATELLITE_ENTITLEMENT_STATUS,
+                COLUMN_SATELLITE_ENTITLEMENT_PLMNS
         );
 
         /**
diff --git a/core/java/android/service/appprediction/AppPredictionService.java b/core/java/android/service/appprediction/AppPredictionService.java
index a2ffa5d..2402cfd 100644
--- a/core/java/android/service/appprediction/AppPredictionService.java
+++ b/core/java/android/service/appprediction/AppPredictionService.java
@@ -18,6 +18,7 @@
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -31,12 +32,15 @@
 import android.app.prediction.IPredictionCallback;
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
+import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.service.appprediction.IPredictionService.Stub;
+import android.service.appprediction.flags.Flags;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
@@ -134,6 +138,16 @@
                     obtainMessage(AppPredictionService::doDestroyPredictionSession,
                             AppPredictionService.this, sessionId));
         }
+
+        @FlaggedApi(Flags.FLAG_SERVICE_FEATURES_API)
+        @Override
+        public void requestServiceFeatures(AppPredictionSessionId sessionId,
+                IRemoteCallback callback) {
+            mHandler.sendMessage(
+                    obtainMessage(AppPredictionService::onRequestServiceFeatures,
+                            AppPredictionService.this, sessionId,
+                            new RemoteCallbackWrapper(callback, null)));
+        }
     };
 
     @CallSuper
@@ -277,6 +291,18 @@
     public void onDestroyPredictionSession(@NonNull AppPredictionSessionId sessionId) {}
 
     /**
+     * Called by the client app to request {@link AppPredictionService} features info.
+     *
+     * @param sessionId the session's Id. It is @NonNull.
+     * @param callback the callback to return the Bundle which includes service features info. It
+     *                is @NonNull.
+     */
+    @FlaggedApi(Flags.FLAG_SERVICE_FEATURES_API)
+    @MainThread
+    public void onRequestServiceFeatures(@NonNull AppPredictionSessionId sessionId,
+            @NonNull Consumer<Bundle> callback) {}
+
+    /**
      * Used by the prediction factory to send back results the client app. The can be called
      * in response to {@link #onRequestPredictionUpdate(AppPredictionSessionId)} or proactively as
      * a result of changes in predictions.
@@ -357,4 +383,50 @@
             }
         }
     }
+
+    private static final class RemoteCallbackWrapper implements Consumer<Bundle>,
+            IBinder.DeathRecipient {
+
+        private IRemoteCallback mCallback;
+        private final Consumer<RemoteCallbackWrapper> mOnBinderDied;
+
+        RemoteCallbackWrapper(IRemoteCallback callback,
+                @Nullable Consumer<RemoteCallbackWrapper> onBinderDied) {
+            mCallback = callback;
+            mOnBinderDied = onBinderDied;
+            if (mOnBinderDied != null) {
+                try {
+                    mCallback.asBinder().linkToDeath(this, 0);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to link to death: " + e);
+                }
+            }
+        }
+
+        public void destroy() {
+            if (mCallback != null && mOnBinderDied != null) {
+                mCallback.asBinder().unlinkToDeath(this, 0);
+            }
+        }
+
+        @Override
+        public void accept(Bundle bundle) {
+            try {
+                if (mCallback != null) {
+                    mCallback.sendResult(bundle);
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error sending result:" + e);
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            destroy();
+            mCallback = null;
+            if (mOnBinderDied != null) {
+                mOnBinderDied.accept(this);
+            }
+        }
+    }
 }
diff --git a/core/java/android/service/appprediction/IPredictionService.aidl b/core/java/android/service/appprediction/IPredictionService.aidl
index 0f3df85..e144dfa 100644
--- a/core/java/android/service/appprediction/IPredictionService.aidl
+++ b/core/java/android/service/appprediction/IPredictionService.aidl
@@ -22,6 +22,7 @@
 import android.app.prediction.AppPredictionSessionId;
 import android.app.prediction.IPredictionCallback;
 import android.content.pm.ParceledListSlice;
+import android.os.IRemoteCallback;
 
 /**
  * Interface from the system to a prediction service.
@@ -50,4 +51,6 @@
     void requestPredictionUpdate(in AppPredictionSessionId sessionId);
 
     void onDestroyPredictionSession(in AppPredictionSessionId sessionId);
+
+    void requestServiceFeatures(in AppPredictionSessionId sessionId, in IRemoteCallback callback);
 }
diff --git a/core/java/android/service/appprediction/flags/flags.aconfig b/core/java/android/service/appprediction/flags/flags.aconfig
new file mode 100644
index 0000000..c7e47d4
--- /dev/null
+++ b/core/java/android/service/appprediction/flags/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.service.appprediction.flags"
+
+flag {
+  name: "service_features_api"
+  namespace: "systemui"
+  description: "Guards the new requestServiceFeatures api"
+  bug: "292565550"
+}
\ No newline at end of file
diff --git a/core/java/android/service/chooser/AdditionalContentContract.java b/core/java/android/service/chooser/AdditionalContentContract.java
new file mode 100644
index 0000000..f679e8a
--- /dev/null
+++ b/core/java/android/service/chooser/AdditionalContentContract.java
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+package android.service.chooser;
+
+import android.annotation.FlaggedApi;
+
+/**
+ * Specifies constants used by Chooser when interacting with the additional content provider,
+ * see {@link android.content.Intent#EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI}.
+ */
+@FlaggedApi(android.service.chooser.Flags.FLAG_CHOOSER_PAYLOAD_TOGGLING)
+public interface AdditionalContentContract {
+
+    interface Columns {
+        /**
+         * Content URI for this item.
+         * <p>
+         * Note that this content URI must have a different authority from the content provided
+         * given in {@link android.content.Intent#EXTRA_CHOOSER_ADDITIONAL_CONTENT_URI}.
+         */
+        String URI = "uri";
+    }
+
+    /**
+     * Constants for {@link android.database.Cursor#getExtras} keys.
+     */
+    interface CursorExtraKeys {
+        /**
+         * An integer, zero-based cursor position that corresponds to the URI specified
+         * with the {@link android.content.Intent#EXTRA_CHOOSER_FOCUSED_ITEM_POSITION} index into
+         * the @link android.content.Intent#EXTRA_STREAM} array.
+         */
+        String POSITION = "position";
+    }
+
+    /**
+     * Constants for method names used with {@link android.content.ContentResolver#call} method.
+     */
+    interface MethodNames {
+        /**
+         * A method name Chooser is using to notify the sharing app about a shared items selection
+         * change.
+         */
+        String ON_SELECTION_CHANGED = "onSelectionChanged";
+    }
+}
diff --git a/core/java/android/service/chooser/ChooserResult.java b/core/java/android/service/chooser/ChooserResult.java
new file mode 100644
index 0000000..4603be1
--- /dev/null
+++ b/core/java/android/service/chooser/ChooserResult.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.chooser;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.compat.annotation.Overridable;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * An event reported to a supplied [IntentSender] by the system chooser when an activity is selected
+ * or other actions are taken to complete the session.
+ *
+ * @see Intent#EXTRA_CHOOSER_RESULT_INTENT_SENDER
+ */
+@FlaggedApi(android.service.chooser.Flags.FLAG_ENABLE_CHOOSER_RESULT)
+public final class ChooserResult implements Parcelable {
+
+    /**
+     * Controls whether to send ChooserResult to the optional IntentSender supplied to the Chooser.
+     * <p>
+     * When enabled, ChooserResult is added to the provided Intent as
+     * {@link Intent#EXTRA_CHOOSER_RESULT}, and sent for actions such as copy and edit, in addition
+     * to activity selection. When disabled, only the selected component
+     * is provided in {@link Intent#EXTRA_CHOSEN_COMPONENT}.
+     * <p>
+     * See: {@link Intent#createChooser(Intent, CharSequence, IntentSender)}
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    @Overridable
+    public static final long SEND_CHOOSER_RESULT = 263474465L;
+
+    /** @hide */
+    @IntDef({
+            CHOOSER_RESULT_UNKNOWN,
+            CHOOSER_RESULT_SELECTED_COMPONENT,
+            CHOOSER_RESULT_COPY,
+            CHOOSER_RESULT_EDIT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ResultType { }
+
+    /** An unknown action was taken to complete the session. */
+    public static final int CHOOSER_RESULT_UNKNOWN = -1;
+    /** The session was completed by selecting an activity to launch. */
+    public static final int CHOOSER_RESULT_SELECTED_COMPONENT = 0;
+    /** The session was completed by invoking the copy action. */
+    public static final int CHOOSER_RESULT_COPY = 1;
+    /** The session was completed by invoking the edit action. */
+    public static final int CHOOSER_RESULT_EDIT = 2;
+
+    @ResultType
+    private final int mType;
+    private final ComponentName mSelectedComponent;
+    private final boolean mIsShortcut;
+
+    private ChooserResult(@NonNull Parcel source) {
+        mType = source.readInt();
+        mSelectedComponent = ComponentName.readFromParcel(source);
+        mIsShortcut = source.readBoolean();
+    }
+
+    /** @hide */
+    public ChooserResult(@ResultType int type, @Nullable ComponentName componentName,
+            boolean isShortcut) {
+        mType = type;
+        mSelectedComponent = componentName;
+        mIsShortcut = isShortcut;
+    }
+
+    /**
+     * The type of the result.
+     *
+     * @return the type of the result
+     */
+    @ResultType
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Provides the component of the Activity selected for results with type
+     * when type is {@link ChooserResult#CHOOSER_RESULT_SELECTED_COMPONENT}.
+     * <p>
+     * For all other types, this value is null.
+     *
+     * @return the component name selected
+     */
+    @Nullable
+    public ComponentName getSelectedComponent() {
+        return mSelectedComponent;
+    }
+
+    /**
+     * Whether the selected component was provided by the app from as a shortcut.
+     *
+     * @return true if the selected component is a shortcut, false otherwise
+     */
+    public boolean isShortcut() {
+        return mIsShortcut;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<ChooserResult> CREATOR =
+            new Creator<>() {
+                @Override
+                public ChooserResult createFromParcel(Parcel source) {
+                    return new ChooserResult(source);
+                }
+
+                @Override
+                public ChooserResult[] newArray(int size) {
+                    return new ChooserResult[0];
+                }
+            };
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mType);
+        ComponentName.writeToParcel(mSelectedComponent, dest);
+        dest.writeBoolean(mIsShortcut);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        ChooserResult that = (ChooserResult) o;
+        return mType == that.mType
+                && mIsShortcut == that.mIsShortcut
+                && Objects.equals(mSelectedComponent, that.mSelectedComponent);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mType, mSelectedComponent, mIsShortcut);
+    }
+}
diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig
index 3cc7f5a..add575b 100644
--- a/core/java/android/service/chooser/flags.aconfig
+++ b/core/java/android/service/chooser/flags.aconfig
@@ -8,6 +8,13 @@
 }
 
 flag {
+    name: "enable_sharesheet_metadata_extra"
+    namespace: "intentresolver"
+    description: "This flag enables sharesheet metadata to be displayed to users."
+    bug: "318942069"
+}
+
+flag {
   name: "support_nfc_resolver"
   namespace: "systemui"
   description: "This flag controls the new NFC 'resolver' activity"
@@ -20,3 +27,10 @@
   description: "This flag controls content toggling in Chooser"
   bug: "302691505"
 }
+
+flag {
+  name: "enable_chooser_result"
+  namespace: "intentresolver"
+  description: "Provides additional callbacks with information about user actions in ChooserResult"
+  bug: "263474465"
+}
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 6410609..2028c40 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -711,18 +711,21 @@
     public void draw(Canvas c, Path highlight, Paint highlightpaint,
                      int cursorOffset) {
         if (mDirect != null && highlight == null) {
+            float leftShift = 0;
             if (getUseBoundsForWidth()) {
-                c.save();
                 RectF drawingRect = computeDrawingBoundingBox();
                 if (drawingRect.left < 0) {
-                    c.translate(-drawingRect.left, 0);
+                    leftShift = -drawingRect.left;
+                    c.translate(leftShift, 0);
                 }
             }
 
             c.drawText(mDirect, 0, mBottom - mDesc, mPaint);
 
-            if (getUseBoundsForWidth()) {
-                c.restore();
+            if (leftShift != 0) {
+                // Manually translate back to the original position because of b/324498002, using
+                // save/restore disappears the toggle switch drawables.
+                c.translate(-leftShift, 0);
             }
         } else {
             super.draw(c, highlight, highlightpaint, cursorOffset);
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 8ddb42d..e5d199a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -464,11 +464,12 @@
             @Nullable Path selectionPath,
             @Nullable Paint selectionPaint,
             int cursorOffsetVertical) {
+        float leftShift = 0;
         if (mUseBoundsForWidth) {
-            canvas.save();
             RectF drawingRect = computeDrawingBoundingBox();
             if (drawingRect.left < 0) {
-                canvas.translate(-drawingRect.left, 0);
+                leftShift = -drawingRect.left;
+                canvas.translate(leftShift, 0);
             }
         }
         final long lineRange = getLineRangeForDraw(canvas);
@@ -479,8 +480,10 @@
         drawWithoutText(canvas, highlightPaths, highlightPaints, selectionPath, selectionPaint,
                 cursorOffsetVertical, firstLine, lastLine);
         drawText(canvas, firstLine, lastLine);
-        if (mUseBoundsForWidth) {
-            canvas.restore();
+        if (leftShift != 0) {
+            // Manually translate back to the original position because of b/324498002, using
+            // save/restore disappears the toggle switch drawables.
+            canvas.translate(-leftShift, 0);
         }
     }
 
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index f37c4c2a..f68fcab9 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -110,3 +110,12 @@
   description: "A flag for replacing AndroidBidi with android.icu.text.Bidi."
   bug: "317144801"
 }
+
+flag {
+  name: "lazy_variation_instance"
+  namespace: "text"
+  description: "A flag for enabling lazy variation instance creation."
+  # Make read only, as it could be used before the Settings provider is initialized.
+  is_fixed_read_only: true
+  bug: "324676775"
+}
diff --git a/core/java/android/tracing/perfetto/TracingContext.java b/core/java/android/tracing/perfetto/TracingContext.java
index 0bce26e..c1a61a7 100644
--- a/core/java/android/tracing/perfetto/TracingContext.java
+++ b/core/java/android/tracing/perfetto/TracingContext.java
@@ -105,6 +105,5 @@
         return res;
     }
 
-    // private static native void nativeFlush(long nativeDataSourcePointer);
     private static native void nativeFlush(TracingContext thiz, long ctxPointer);
 }
diff --git a/core/java/android/util/Xml.java b/core/java/android/util/Xml.java
index ec6e90b..50d419f 100644
--- a/core/java/android/util/Xml.java
+++ b/core/java/android/util/Xml.java
@@ -53,9 +53,12 @@
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 
+import javax.xml.parsers.SAXParserFactory;
+
 /**
  * XML utility methods.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Xml {
     private Xml() {}
 
@@ -73,8 +76,33 @@
      *
      * @hide
      */
-    public static final boolean ENABLE_BINARY_DEFAULT = SystemProperties
-            .getBoolean("persist.sys.binary_xml", true);
+    public static final boolean ENABLE_BINARY_DEFAULT = shouldEnableBinaryDefault();
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static boolean shouldEnableBinaryDefault() {
+        return SystemProperties.getBoolean("persist.sys.binary_xml", true);
+    }
+
+    private static boolean shouldEnableBinaryDefault$ravenwood() {
+        return true;
+    }
+
+    /**
+     * Feature flag: when set, {@link #resolvePullParser(InputStream)}} will attempt to sniff
+     * using {@code pread} optimization.
+     *
+     * @hide
+     */
+    public static final boolean ENABLE_RESOLVE_OPTIMIZATIONS = shouldEnableResolveOptimizations();
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static boolean shouldEnableResolveOptimizations() {
+        return true;
+    }
+
+    private static boolean shouldEnableResolveOptimizations$ravenwood() {
+        return false;
+    }
 
     /**
      * Parses the given xml string and fires events on the given SAX handler.
@@ -82,7 +110,7 @@
     public static void parse(String xml, ContentHandler contentHandler)
             throws SAXException {
         try {
-            XMLReader reader = XmlObjectFactory.newXMLReader();
+            XMLReader reader = newXMLReader();
             reader.setContentHandler(contentHandler);
             reader.parse(new InputSource(new StringReader(xml)));
         } catch (IOException e) {
@@ -96,7 +124,7 @@
      */
     public static void parse(Reader in, ContentHandler contentHandler)
             throws IOException, SAXException {
-        XMLReader reader = XmlObjectFactory.newXMLReader();
+        XMLReader reader = newXMLReader();
         reader.setContentHandler(contentHandler);
         reader.parse(new InputSource(in));
     }
@@ -107,7 +135,7 @@
      */
     public static void parse(InputStream in, Encoding encoding,
             ContentHandler contentHandler) throws IOException, SAXException {
-        XMLReader reader = XmlObjectFactory.newXMLReader();
+        XMLReader reader = newXMLReader();
         reader.setContentHandler(contentHandler);
         InputSource source = new InputSource(in);
         source.setEncoding(encoding.expatName);
@@ -120,19 +148,26 @@
     @android.ravenwood.annotation.RavenwoodReplace
     public static XmlPullParser newPullParser() {
         try {
-            XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
+            XmlPullParser parser = newXmlPullParser();
             parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
             parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
             return parser;
         } catch (XmlPullParserException e) {
-            throw new AssertionError();
+            throw new AssertionError(e);
         }
     }
 
     /** @hide */
     public static XmlPullParser newPullParser$ravenwood() {
-        // TODO: remove once we're linking against libcore
-        return new BinaryXmlPullParser();
+        try {
+            // Prebuilt kxml2-android does not support FEATURE_PROCESS_DOCDECL, so omit here;
+            // it's quite rare and all tests are passing
+            XmlPullParser parser = newXmlPullParser();
+            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
+            return parser;
+        } catch (XmlPullParserException e) {
+            throw new AssertionError(e);
+        }
     }
 
     /**
@@ -145,17 +180,10 @@
      * @hide
      */
     @SuppressWarnings("AndroidFrameworkEfficientXml")
-    @android.ravenwood.annotation.RavenwoodReplace
     public static @NonNull TypedXmlPullParser newFastPullParser() {
         return XmlUtils.makeTyped(newPullParser());
     }
 
-    /** @hide */
-    public static TypedXmlPullParser newFastPullParser$ravenwood() {
-        // TODO: remove once we're linking against libcore
-        return new BinaryXmlPullParser();
-    }
-
     /**
      * Creates a new {@link XmlPullParser} that reads XML documents using a
      * custom binary wire protocol which benchmarking has shown to be 8.5x
@@ -189,11 +217,10 @@
      *
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodReplace
     public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
             throws IOException {
         final byte[] magic = new byte[4];
-        if (in instanceof FileInputStream) {
+        if (ENABLE_RESOLVE_OPTIMIZATIONS && in instanceof FileInputStream) {
             try {
                 Os.pread(((FileInputStream) in).getFD(), magic, 0, magic.length, 0);
             } catch (ErrnoException e) {
@@ -222,31 +249,11 @@
         return xml;
     }
 
-    /** @hide */
-    public static @NonNull TypedXmlPullParser resolvePullParser$ravenwood(@NonNull InputStream in)
-            throws IOException {
-        // TODO: remove once we're linking against libcore
-        final TypedXmlPullParser xml = new BinaryXmlPullParser();
-        try {
-            xml.setInput(in, StandardCharsets.UTF_8.name());
-        } catch (XmlPullParserException e) {
-            throw new IOException(e);
-        }
-        return xml;
-    }
-
     /**
      * Creates a new xml serializer.
      */
-    @android.ravenwood.annotation.RavenwoodReplace
     public static XmlSerializer newSerializer() {
-        return XmlObjectFactory.newXmlSerializer();
-    }
-
-    /** @hide */
-    public static XmlSerializer newSerializer$ravenwood() {
-        // TODO: remove once we're linking against libcore
-        return new BinaryXmlSerializer();
+        return newXmlSerializer();
     }
 
     /**
@@ -259,17 +266,10 @@
      * @hide
      */
     @SuppressWarnings("AndroidFrameworkEfficientXml")
-    @android.ravenwood.annotation.RavenwoodReplace
     public static @NonNull TypedXmlSerializer newFastSerializer() {
         return XmlUtils.makeTyped(new FastXmlSerializer());
     }
 
-    /** @hide */
-    public static @NonNull TypedXmlSerializer newFastSerializer$ravenwood() {
-        // TODO: remove once we're linking against libcore
-        return new BinaryXmlSerializer();
-    }
-
     /**
      * Creates a new {@link XmlSerializer} that writes XML documents using a
      * custom binary wire protocol which benchmarking has shown to be 4.4x
@@ -334,7 +334,6 @@
      *
      * @hide
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     public static void copy(@NonNull XmlPullParser in, @NonNull XmlSerializer out)
             throws XmlPullParserException, IOException {
         // Some parsers may have already consumed the event that starts the
@@ -394,7 +393,6 @@
      * unsupported, which can confuse serializers. This method normalizes empty
      * strings to be {@code null}.
      */
-    @android.ravenwood.annotation.RavenwoodKeep
     private static @Nullable String normalizeNamespace(@Nullable String namespace) {
         if (namespace == null || namespace.isEmpty()) {
             return null;
@@ -457,4 +455,45 @@
                 ? (AttributeSet) parser
                 : new XmlPullAttributes(parser);
     }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static @NonNull XmlSerializer newXmlSerializer() {
+        return XmlObjectFactory.newXmlSerializer();
+    }
+
+    private static @NonNull XmlSerializer newXmlSerializer$ravenwood() {
+        try {
+            return XmlPullParserFactory.newInstance().newSerializer();
+        } catch (XmlPullParserException e) {
+            throw new UnsupportedOperationException(e);
+        }
+    }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static @NonNull XmlPullParser newXmlPullParser() {
+        return XmlObjectFactory.newXmlPullParser();
+    }
+
+    private static @NonNull XmlPullParser newXmlPullParser$ravenwood() {
+        try {
+            return XmlPullParserFactory.newInstance().newPullParser();
+        } catch (XmlPullParserException e) {
+            throw new UnsupportedOperationException(e);
+        }
+    }
+
+    @android.ravenwood.annotation.RavenwoodReplace
+    private static @NonNull XMLReader newXMLReader() {
+        return XmlObjectFactory.newXMLReader();
+    }
+
+    private static @NonNull XMLReader newXMLReader$ravenwood() {
+        try {
+            final SAXParserFactory factory = SAXParserFactory.newInstance();
+            factory.setNamespaceAware(true);
+            return factory.newSAXParser().getXMLReader();
+        } catch (Exception e) {
+            throw new UnsupportedOperationException(e);
+        }
+    }
 }
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 0ce1d47..eb28920 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -23,8 +23,10 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
+import android.widget.Editor;
 import android.widget.TextView;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -80,6 +82,16 @@
      * connections and only set mConnectedView to null when mConnectionCount is zero.
      */
     private int mConnectionCount = 0;
+
+    /**
+     * The reference to the View that currently has focus.
+     * This replaces mConnecteView when {@code Flags#intitiationWithoutInputConnection()} is
+     * enabled.
+     */
+    @Nullable
+    @VisibleForTesting
+    public WeakReference<View> mFocusedView = null;
+
     private final InputMethodManager mImm;
 
     private final int[] mTempLocation = new int[2];
@@ -112,9 +124,15 @@
      *
      * If the stylus is hovering on an unconnected editor that supports handwriting, we always show
      * the hover icon.
+     * TODO(b/308827131): Rename to FocusedView after Flag is flipped.
      */
     private boolean mShowHoverIconForConnectedView = true;
 
+    /** When flag is enabled, touched editors don't wait for InputConnection for initiation.
+     * However, delegation still waits for InputConnection.
+     */
+    private final boolean mInitiateWithoutConnection = Flags.initiationWithoutInputConnection();
+
     @VisibleForTesting
     public HandwritingInitiator(@NonNull ViewConfiguration viewConfiguration,
             @NonNull InputMethodManager inputMethodManager) {
@@ -201,8 +219,8 @@
                     View candidateView = findBestCandidateView(mState.mStylusDownX,
                             mState.mStylusDownY, /* isHover */ false);
                     if (candidateView != null) {
-                        if (candidateView == getConnectedView()) {
-                            if (!candidateView.hasFocus()) {
+                        if (candidateView == getConnectedOrFocusedView()) {
+                            if (!mInitiateWithoutConnection && !candidateView.hasFocus()) {
                                 requestFocusWithoutReveal(candidateView);
                             }
                             startHandwriting(candidateView);
@@ -217,8 +235,17 @@
                             candidateView.getHandwritingDelegatorCallback().run();
                             mState.mHasPreparedHandwritingDelegation = true;
                         } else {
-                            mState.mPendingConnectedView = new WeakReference<>(candidateView);
-                            requestFocusWithoutReveal(candidateView);
+                            if (!mInitiateWithoutConnection) {
+                                mState.mPendingConnectedView = new WeakReference<>(candidateView);
+                            }
+                            if (!candidateView.hasFocus()) {
+                                requestFocusWithoutReveal(candidateView);
+                            }
+                            if (mInitiateWithoutConnection
+                                    && updateFocusedView(candidateView,
+                                            /* fromTouchEvent */ true)) {
+                                startHandwriting(candidateView);
+                            }
                         }
                     }
                 }
@@ -244,11 +271,7 @@
      */
     public void onDelegateViewFocused(@NonNull View view) {
         if (view == getConnectedView()) {
-            if (tryAcceptStylusHandwritingDelegation(view)) {
-                // A handwriting delegate view is accepted and handwriting starts; hide the
-                // hover icon.
-                mShowHoverIconForConnectedView = false;
-            }
+            tryAcceptStylusHandwritingDelegation(view);
         }
     }
 
@@ -260,6 +283,10 @@
      * @see  #onInputConnectionClosed(View)
      */
     public void onInputConnectionCreated(@NonNull View view) {
+        if (mInitiateWithoutConnection && !view.isHandwritingDelegate()) {
+            // When flag is enabled, only delegation continues to wait for InputConnection.
+            return;
+        }
         if (!view.isAutoHandwritingEnabled()) {
             clearConnectedView();
             return;
@@ -274,12 +301,15 @@
             // A new view just gain focus. By default, we should show hover icon for it.
             mShowHoverIconForConnectedView = true;
             if (view.isHandwritingDelegate() && tryAcceptStylusHandwritingDelegation(view)) {
-                // A handwriting delegate view is accepted and handwriting starts; hide the
-                // hover icon.
+                // tryAcceptStylusHandwritingDelegation should set boolean below, however, we
+                // cannot mock IMM to return true for acceptStylusDelegation().
+                // TODO(b/324670412): we should move any dependent tests to integration and remove
+                //  the assignment below.
                 mShowHoverIconForConnectedView = false;
                 return;
             }
-            if (mState != null && mState.mPendingConnectedView != null
+            if (!mInitiateWithoutConnection && mState != null
+                    && mState.mPendingConnectedView != null
                     && mState.mPendingConnectedView.get() == view) {
                 startHandwriting(view);
             }
@@ -293,6 +323,9 @@
      * @param view the view that closed the InputConnection.
      */
     public void onInputConnectionClosed(@NonNull View view) {
+        if (mInitiateWithoutConnection && !view.isHandwritingDelegate()) {
+            return;
+        }
         final View connectedView = getConnectedView();
         if (connectedView == null) return;
         if (connectedView == view) {
@@ -306,6 +339,48 @@
         }
     }
 
+    @Nullable
+    private View getFocusedView() {
+        if (mFocusedView == null) return null;
+        return mFocusedView.get();
+    }
+
+    /**
+     * Clear the tracked focused view tracked for handwriting initiation.
+     * @param view the focused view.
+     */
+    public void clearFocusedView(View view) {
+        if (view == null || mFocusedView == null) {
+            return;
+        }
+        if (mFocusedView.get() == view) {
+            mFocusedView = null;
+        }
+    }
+
+    /**
+     * Called when new {@link Editor} is focused.
+     * @return {@code true} if handwriting can initiate for given view.
+     */
+    @VisibleForTesting
+    public boolean updateFocusedView(@NonNull View view, boolean fromTouchEvent) {
+        if (!view.shouldInitiateHandwriting()) {
+            mFocusedView = null;
+            return false;
+        }
+
+        final View focusedView = getFocusedView();
+        if (focusedView != view) {
+            mFocusedView = new WeakReference<>(view);
+            // A new view just gain focus. By default, we should show hover icon for it.
+            mShowHoverIconForConnectedView = true;
+        }
+        if (!fromTouchEvent) {
+            tryAcceptStylusHandwritingDelegation(view);
+        }
+        return true;
+    }
+
     /** Starts a stylus handwriting session for the view. */
     @VisibleForTesting
     public void startHandwriting(@NonNull View view) {
@@ -324,6 +399,9 @@
      */
     @VisibleForTesting
     public boolean tryAcceptStylusHandwritingDelegation(@NonNull View view) {
+        if (!view.isHandwritingDelegate() || (mState != null && mState.mHasInitiatedHandwriting)) {
+            return false;
+        }
         String delegatorPackageName =
                 view.getAllowedHandwritingDelegatorPackageName();
         if (delegatorPackageName == null) {
@@ -337,6 +415,9 @@
             if (view instanceof TextView) {
                 ((TextView) view).hideHint();
             }
+            // A handwriting delegate view is accepted and handwriting starts; hide the
+            // hover icon.
+            mShowHoverIconForConnectedView = false;
             return true;
         }
         return false;
@@ -377,16 +458,25 @@
             return PointerIcon.getSystemIcon(context, PointerIcon.TYPE_HANDWRITING);
         }
 
-        if (hoverView != getConnectedView()) {
+        if (hoverView != getConnectedOrFocusedView()) {
             // The stylus is hovering on another view that supports handwriting. We should show
-            // hover icon. Also reset the mShowHoverIconForConnectedView so that hover
-            // icon is displayed again next time when the stylus hovers on connected view.
+            // hover icon. Also reset the mShowHoverIconForFocusedView so that hover
+            // icon is displayed again next time when the stylus hovers on focused view.
             mShowHoverIconForConnectedView = true;
             return PointerIcon.getSystemIcon(context, PointerIcon.TYPE_HANDWRITING);
         }
         return null;
     }
 
+    // TODO(b/308827131): Remove once Flag is flipped.
+    private View getConnectedOrFocusedView() {
+        if (mInitiateWithoutConnection) {
+            return mFocusedView == null ? null : mFocusedView.get();
+        } else {
+            return mConnectedView == null ? null : mConnectedView.get();
+        }
+    }
+
     private View getCachedHoverTarget() {
         if (mCachedHoverTarget == null) {
             return null;
@@ -458,20 +548,21 @@
      */
     @Nullable
     private View findBestCandidateView(float x, float y, boolean isHover) {
+        // TODO(b/308827131): Rename to FocusedView after Flag is flipped.
         // If the connectedView is not null and do not set any handwriting area, it will check
         // whether the connectedView's boundary contains the initial stylus position. If true,
         // directly return the connectedView.
-        final View connectedView = getConnectedView();
-        if (connectedView != null) {
+        final View connectedOrFocusedView = getConnectedOrFocusedView();
+        if (connectedOrFocusedView != null) {
             Rect handwritingArea = mTempRect;
-            if (getViewHandwritingArea(connectedView, handwritingArea)
-                    && isInHandwritingArea(handwritingArea, x, y, connectedView, isHover)
-                    && shouldTriggerStylusHandwritingForView(connectedView)) {
+            if (getViewHandwritingArea(connectedOrFocusedView, handwritingArea)
+                    && isInHandwritingArea(handwritingArea, x, y, connectedOrFocusedView, isHover)
+                    && shouldTriggerStylusHandwritingForView(connectedOrFocusedView)) {
                 if (!isHover && mState != null) {
                     mState.mStylusDownWithinEditorBounds =
                             contains(handwritingArea, x, y, 0f, 0f, 0f, 0f);
                 }
-                return connectedView;
+                return connectedOrFocusedView;
             }
         }
 
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index b5b81d1..29cc859 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -73,6 +73,7 @@
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
 import android.window.ITrustedPresentationListener;
+import android.window.IUnhandledDragListener;
 import android.window.InputTransferToken;
 import android.window.ScreenCapture;
 import android.window.TrustedPresentationThresholds;
@@ -1091,4 +1092,10 @@
 
     @EnforcePermission("DETECT_SCREEN_RECORDING")
     void unregisterScreenRecordingCallback(IScreenRecordingCallback callback);
+
+    /**
+     * Sets the listener to be called back when a cross-window drag and drop operation is unhandled
+     * (ie. not handled by any window which can handle the drag).
+     */
+    void setUnhandledDragListener(IUnhandledDragListener listener);
 }
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index 83bdb08..fe98fab 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.IBinder;
@@ -110,6 +111,12 @@
     private Insets mMinimalInsetsSizeInDisplayCutoutSafe = null;
 
     /**
+     * Indicates the bounding rectangles within the provided insets frame, in relative coordinates
+     * to the source frame.
+     */
+    private Rect[] mBoundingRects = null;
+
+    /**
      * Creates an InsetsFrameProvider which describes what frame an insets source should have.
      *
      * @param owner the owner of this provider. We might have multiple sources with the same type on
@@ -205,6 +212,22 @@
         return mMinimalInsetsSizeInDisplayCutoutSafe;
     }
 
+    /**
+     * Sets the bounding rectangles within and relative to the source frame.
+     */
+    public InsetsFrameProvider setBoundingRects(@Nullable Rect[] boundingRects) {
+        mBoundingRects = boundingRects == null ? null : boundingRects.clone();
+        return this;
+    }
+
+    /**
+     * Returns the arbitrary bounding rects, or null if none were set.
+     */
+    @Nullable
+    public Rect[] getBoundingRects() {
+        return mBoundingRects;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -231,6 +254,9 @@
             sb.append(", mMinimalInsetsSizeInDisplayCutoutSafe=")
                     .append(mMinimalInsetsSizeInDisplayCutoutSafe);
         }
+        if (mBoundingRects != null) {
+            sb.append(", mBoundingRects=").append(Arrays.toString(mBoundingRects));
+        }
         sb.append("}");
         return sb.toString();
     }
@@ -257,6 +283,7 @@
         mInsetsSizeOverrides = in.createTypedArray(InsetsSizeOverride.CREATOR);
         mArbitraryRectangle = in.readTypedObject(Rect.CREATOR);
         mMinimalInsetsSizeInDisplayCutoutSafe = in.readTypedObject(Insets.CREATOR);
+        mBoundingRects = in.createTypedArray(Rect.CREATOR);
     }
 
     @Override
@@ -268,6 +295,7 @@
         out.writeTypedArray(mInsetsSizeOverrides, flags);
         out.writeTypedObject(mArbitraryRectangle, flags);
         out.writeTypedObject(mMinimalInsetsSizeInDisplayCutoutSafe, flags);
+        out.writeTypedArray(mBoundingRects, flags);
     }
 
     public boolean idEquals(InsetsFrameProvider o) {
@@ -288,14 +316,15 @@
                 && Arrays.equals(mInsetsSizeOverrides, other.mInsetsSizeOverrides)
                 && Objects.equals(mArbitraryRectangle, other.mArbitraryRectangle)
                 && Objects.equals(mMinimalInsetsSizeInDisplayCutoutSafe,
-                        other.mMinimalInsetsSizeInDisplayCutoutSafe);
+                        other.mMinimalInsetsSizeInDisplayCutoutSafe)
+                && Arrays.equals(mBoundingRects, other.mBoundingRects);
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mId, mSource, mFlags, mInsetsSize,
                 Arrays.hashCode(mInsetsSizeOverrides), mArbitraryRectangle,
-                mMinimalInsetsSizeInDisplayCutoutSafe);
+                mMinimalInsetsSizeInDisplayCutoutSafe, Arrays.hashCode(mBoundingRects));
     }
 
     public static final @NonNull Parcelable.Creator<InsetsFrameProvider> CREATOR =
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index bc33d5e..f9eba29 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -38,6 +38,8 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.StringJoiner;
 
@@ -105,6 +107,12 @@
     })
     public @interface Flags {}
 
+    /**
+     * Used when there are no bounding rects to describe an inset, which is only possible when the
+     * insets itself is {@link Insets#NONE}.
+     */
+    private static final Rect[] NO_BOUNDING_RECTS = new Rect[0];
+
     private @Flags int mFlags;
 
     /**
@@ -117,6 +125,7 @@
     /** Frame of the source in screen coordinate space */
     private final Rect mFrame;
     private @Nullable Rect mVisibleFrame;
+    private @Nullable Rect[] mBoundingRects;
 
     private boolean mVisible;
 
@@ -127,6 +136,7 @@
     private @InternalInsetsSide int mSideHint = SIDE_NONE;
 
     private final Rect mTmpFrame = new Rect();
+    private final Rect mTmpBoundingRect = new Rect();
 
     public InsetsSource(int id, @InsetsType int type) {
         mId = id;
@@ -145,6 +155,9 @@
                 : null;
         mFlags = other.mFlags;
         mSideHint = other.mSideHint;
+        mBoundingRects = other.mBoundingRects != null
+                ? other.mBoundingRects.clone()
+                : null;
     }
 
     public void set(InsetsSource other) {
@@ -155,6 +168,9 @@
                 : null;
         mFlags = other.mFlags;
         mSideHint = other.mSideHint;
+        mBoundingRects = other.mBoundingRects != null
+                ? other.mBoundingRects.clone()
+                : null;
     }
 
     public InsetsSource setFrame(int left, int top, int right, int bottom) {
@@ -199,6 +215,15 @@
         return this;
     }
 
+    /**
+     * Set the bounding rectangles of this source. They are expected to be relative to the source
+     * frame.
+     */
+    public InsetsSource setBoundingRects(@Nullable Rect[] rects) {
+        mBoundingRects = rects != null ? rects.clone() : null;
+        return this;
+    }
+
     public int getId() {
         return mId;
     }
@@ -228,6 +253,13 @@
     }
 
     /**
+     * Returns the bounding rectangles of this source.
+     */
+    public @Nullable Rect[] getBoundingRects() {
+        return mBoundingRects;
+    }
+
+    /**
      * Calculates the insets this source will cause to a client window.
      *
      * @param relativeFrame The frame to calculate the insets relative to.
@@ -313,6 +345,82 @@
     }
 
     /**
+     * Calculates the bounding rects the source will cause to a client window.
+     */
+    public @NonNull Rect[] calculateBoundingRects(Rect relativeFrame, boolean ignoreVisibility) {
+        if (!ignoreVisibility && !mVisible) {
+            return NO_BOUNDING_RECTS;
+        }
+
+        final Rect frame = getFrame();
+        if (mBoundingRects == null) {
+            // No bounding rects set, make a single bounding rect that covers the intersection of
+            // the |frame| and the |relativeFrame|.
+            return mTmpBoundingRect.setIntersect(frame, relativeFrame)
+                    ? new Rect[]{ new Rect(mTmpBoundingRect) }
+                    : NO_BOUNDING_RECTS;
+
+        }
+
+        // Special treatment for captionBar inset type. During drag-resizing, the |frame| and
+        // |boundingRects| may not get updated as quickly as |relativeFrame|, so just assume the
+        // |frame| will always be either at the top or bottom of |relativeFrame|. This means some
+        // calculations to make |boundingRects| relative to |relativeFrame| can be skipped or
+        // simplified.
+        // TODO(b/254128050): remove special treatment.
+        if (getType() == WindowInsets.Type.captionBar()) {
+            final ArrayList<Rect> validBoundingRects = new ArrayList<>();
+            for (final Rect boundingRect : mBoundingRects) {
+                // Assume that the caption |frame| and |relativeFrame| perfectly align at the top
+                // or bottom, meaning that the provided |boundingRect|, which is relative to the
+                // |frame| either is already relative to |relativeFrame| (for top captionBar()), or
+                // just needs to be made relative to |relativeFrame| for bottom bars.
+                final int frameHeight = frame.height();
+                mTmpBoundingRect.set(boundingRect);
+                if (getId() == ID_IME_CAPTION_BAR) {
+                    mTmpBoundingRect.offset(0, relativeFrame.height() - frameHeight);
+                }
+                validBoundingRects.add(new Rect(mTmpBoundingRect));
+            }
+            return validBoundingRects.toArray(new Rect[validBoundingRects.size()]);
+        }
+
+        // Regular treatment for non-captionBar inset types.
+        final ArrayList<Rect> validBoundingRects = new ArrayList<>();
+        for (final Rect boundingRect : mBoundingRects) {
+            // |boundingRect| was provided relative to |frame|. Make it absolute to be in the same
+            // coordinate system as |frame|.
+            final Rect absBoundingRect = new Rect(
+                    boundingRect.left + frame.left,
+                    boundingRect.top + frame.top,
+                    boundingRect.right + frame.left,
+                    boundingRect.bottom + frame.top
+            );
+            // Now find the intersection of that |absBoundingRect| with |relativeFrame|. In other
+            // words, whichever part of the bounding rect is inside the window frame.
+            if (!mTmpBoundingRect.setIntersect(absBoundingRect, relativeFrame)) {
+                // It's possible for this to be empty if the frame and bounding rects were larger
+                // than the |relativeFrame|, such as when a system window is wider than the app
+                // window width. Just ignore that rect since it will have no effect on the
+                // window insets.
+                continue;
+            }
+            // At this point, |mTmpBoundingRect| is a valid bounding rect located fully inside the
+            // window, convert it to be relative to the window so that apps don't need to know the
+            // location of the window to understand bounding rects.
+            validBoundingRects.add(new Rect(
+                    mTmpBoundingRect.left - relativeFrame.left,
+                    mTmpBoundingRect.top - relativeFrame.top,
+                    mTmpBoundingRect.right - relativeFrame.left,
+                    mTmpBoundingRect.bottom - relativeFrame.top));
+        }
+        if (validBoundingRects.isEmpty()) {
+            return NO_BOUNDING_RECTS;
+        }
+        return validBoundingRects.toArray(new Rect[validBoundingRects.size()]);
+    }
+
+    /**
      * Outputs the intersection of two rectangles. The shared edges will also be counted in the
      * intersection.
      *
@@ -467,6 +575,7 @@
         pw.print(" visible="); pw.print(mVisible);
         pw.print(" flags="); pw.print(flagsToString(mFlags));
         pw.print(" sideHint="); pw.print(sideToString(mSideHint));
+        pw.print(" boundingRects="); pw.print(Arrays.toString(mBoundingRects));
         pw.println();
     }
 
@@ -492,12 +601,14 @@
         if (mSideHint != that.mSideHint) return false;
         if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true;
         if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
-        return mFrame.equals(that.mFrame);
+        if (!mFrame.equals(that.mFrame)) return false;
+        return Arrays.equals(mBoundingRects, that.mBoundingRects);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags, mSideHint);
+        return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags, mSideHint,
+                Arrays.hashCode(mBoundingRects));
     }
 
     public InsetsSource(Parcel in) {
@@ -512,6 +623,7 @@
         mVisible = in.readBoolean();
         mFlags = in.readInt();
         mSideHint = in.readInt();
+        mBoundingRects = in.createTypedArray(Rect.CREATOR);
     }
 
     @Override
@@ -533,6 +645,7 @@
         dest.writeBoolean(mVisible);
         dest.writeInt(mFlags);
         dest.writeInt(mSideHint);
+        dest.writeTypedArray(mBoundingRects, flags);
     }
 
     @Override
@@ -543,6 +656,7 @@
                 + " mVisible=" + mVisible
                 + " mFlags=" + flagsToString(mFlags)
                 + " mSideHint=" + sideToString(mSideHint)
+                + " mBoundingRects=" + Arrays.toString(mBoundingRects)
                 + "}";
     }
 
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index c88da9e..21eec67 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -128,6 +128,8 @@
         final Rect relativeFrameMax = new Rect(frame);
         @InsetsType int forceConsumingTypes = 0;
         @InsetsType int suppressScrimTypes = 0;
+        final Rect[][] typeBoundingRectsMap = new Rect[Type.SIZE][];
+        final Rect[][] typeMaxBoundingRectsMap = new Rect[Type.SIZE][];
         for (int i = mSources.size() - 1; i >= 0; i--) {
             final InsetsSource source = mSources.valueAt(i);
             final @InsetsType int type = source.getType();
@@ -141,7 +143,7 @@
             }
 
             processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
-                    idSideMap, typeVisibilityMap);
+                    idSideMap, typeVisibilityMap, typeBoundingRectsMap);
 
             // IME won't be reported in max insets as the size depends on the EditorInfo of the IME
             // target.
@@ -154,7 +156,7 @@
                 }
                 processSource(ignoringVisibilitySource, relativeFrameMax,
                         true /* ignoreVisibility */, typeMaxInsetsMap, null /* idSideMap */,
-                        null /* typeVisibilityMap */);
+                        null /* typeVisibilityMap */, typeMaxBoundingRectsMap);
             }
         }
         final int softInputAdjustMode = legacySoftInputMode & SOFT_INPUT_MASK_ADJUST;
@@ -175,7 +177,8 @@
                 calculateRelativeRoundedCorners(frame),
                 calculateRelativePrivacyIndicatorBounds(frame),
                 calculateRelativeDisplayShape(frame),
-                compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0);
+                compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0,
+                typeBoundingRectsMap, typeMaxBoundingRectsMap, frame.width(), frame.height());
     }
 
     private DisplayCutout calculateRelativeCutout(Rect frame) {
@@ -328,12 +331,13 @@
 
     private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
             Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray idSideMap,
-            @Nullable boolean[] typeVisibilityMap) {
+            @Nullable boolean[] typeVisibilityMap, Rect[][] typeBoundingRectsMap) {
         Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
+        final Rect[] boundingRects = source.calculateBoundingRects(relativeFrame, ignoreVisibility);
 
         final int type = source.getType();
         processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
-                insets, type);
+                typeBoundingRectsMap, insets, boundingRects, type);
 
         if (type == Type.MANDATORY_SYSTEM_GESTURES) {
             // Mandatory system gestures are also system gestures.
@@ -342,24 +346,25 @@
             //       ability to set systemGestureInsets() independently from
             //       mandatorySystemGestureInsets() in the Builder.
             processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
-                    insets, Type.SYSTEM_GESTURES);
+                    typeBoundingRectsMap, insets, boundingRects, Type.SYSTEM_GESTURES);
         }
         if (type == Type.CAPTION_BAR) {
             // Caption should also be gesture and tappable elements. This should not be needed when
             // the caption is added from the shell, as the shell can add other types at the same
             // time.
             processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
-                    insets, Type.SYSTEM_GESTURES);
+                    typeBoundingRectsMap, insets, boundingRects, Type.SYSTEM_GESTURES);
             processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
-                    insets, Type.MANDATORY_SYSTEM_GESTURES);
+                    typeBoundingRectsMap, insets, boundingRects, Type.MANDATORY_SYSTEM_GESTURES);
             processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
-                    insets, Type.TAPPABLE_ELEMENT);
+                    typeBoundingRectsMap, insets, boundingRects, Type.TAPPABLE_ELEMENT);
         }
     }
 
     private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap,
             @InternalInsetsSide @Nullable SparseIntArray idSideMap,
-            @Nullable boolean[] typeVisibilityMap, Insets insets, int type) {
+            @Nullable boolean[] typeVisibilityMap, Rect[][] typeBoundingRectsMap,
+            Insets insets, Rect[] boundingRects, int type) {
         int index = indexOf(type);
 
         // Don't put Insets.NONE into typeInsetsMap. Otherwise, two WindowInsets can be considered
@@ -384,6 +389,22 @@
                 idSideMap.put(source.getId(), insetSide);
             }
         }
+
+        if (typeBoundingRectsMap != null && boundingRects.length > 0) {
+            final Rect[] existing = typeBoundingRectsMap[index];
+            if (existing == null) {
+                typeBoundingRectsMap[index] = boundingRects;
+            } else {
+                typeBoundingRectsMap[index] = concatenate(existing, boundingRects);
+            }
+        }
+    }
+
+    private static Rect[] concatenate(Rect[] a, Rect[] b) {
+        final Rect[] c = new Rect[a.length + b.length];
+        System.arraycopy(a, 0, c, 0, a.length);
+        System.arraycopy(b, 0, c, a.length, b.length);
+        return c;
     }
 
     /**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c22986b..dc0b1a7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -43,6 +43,7 @@
 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS;
 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP;
 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION;
+import static com.android.window.flags.Flags.FLAG_DELEGATE_UNHANDLED_DRAGS;
 
 import static java.lang.Math.max;
 
@@ -68,6 +69,7 @@
 import android.annotation.TestApi;
 import android.annotation.UiContext;
 import android.annotation.UiThread;
+import android.app.PendingIntent;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AutofillOptions;
 import android.content.ClipData;
@@ -5329,6 +5331,34 @@
     public static final int DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION = 1 << 11;
 
     /**
+     * Flag indicating that a drag can cross window boundaries (within the same application).  When
+     * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called
+     * with this flag set, only visible windows belonging to the same application (ie. share the
+     * same UID) with targetSdkVersion >= {@link android.os.Build.VERSION_CODES#N API 24} will be
+     * able to participate in the drag operation and receive the dragged content.
+     *
+     * If both DRAG_FLAG_GLOBAL_SAME_APPLICATION and DRAG_FLAG_GLOBAL are set, then
+     * DRAG_FLAG_GLOBAL_SAME_APPLICATION takes precedence and the drag will only go to visible
+     * windows from the same application.
+     */
+    @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+    public static final int DRAG_FLAG_GLOBAL_SAME_APPLICATION = 1 << 12;
+
+    /**
+     * Flag indicating that an unhandled drag should be delegated to the system to be started if no
+     * visible window wishes to handle the drop. When using this flag, the caller must provide
+     * ClipData with an Item that contains an immutable PendingIntent to an activity to be launched
+     * (not a broadcast, service, etc).  See
+     * {@link ClipData.Item.Builder#setPendingIntent(PendingIntent)}.
+     *
+     * The system can decide to launch the intent or not based on factors like the current screen
+     * size or windowing mode. If the system does not launch the intent, it will be canceled via the
+     * normal drag and drop flow.
+     */
+    @FlaggedApi(FLAG_DELEGATE_UNHANDLED_DRAGS)
+    public static final int DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG = 1 << 13;
+
+    /**
      * Vertical scroll factor cached by {@link #getVerticalScrollFactor}.
      */
     private float mVerticalScrollFactor;
@@ -6364,6 +6394,9 @@
                 case R.styleable.View_handwritingBoundsOffsetBottom:
                     mHandwritingBoundsOffsetBottom = a.getDimension(attr, 0);
                     break;
+                case R.styleable.View_contentSensitivity:
+                    setContentSensitivity(a.getInt(attr, CONTENT_SENSITIVITY_AUTO));
+                    break;
             }
         }
 
@@ -22269,6 +22302,9 @@
      * Retrieve a unique token identifying the window this view is attached to.
      * @return Return the window's token for use in
      * {@link WindowManager.LayoutParams#token WindowManager.LayoutParams.token}.
+     * This token maybe null if this view is not attached to a window.
+     * @see #isAttachedToWindow() for current window attach state
+     * @see OnAttachStateChangeListener to listen to window attach/detach state changes
      */
     public IBinder getWindowToken() {
         return mAttachInfo != null ? mAttachInfo.mWindowToken : null;
@@ -28496,9 +28532,29 @@
             Log.w(VIEW_LOG_TAG, "startDragAndDrop called with an invalid surface.");
             return false;
         }
+        if ((flags & DRAG_FLAG_GLOBAL) != 0 && ((flags & DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0)) {
+            Log.w(VIEW_LOG_TAG, "startDragAndDrop called with both DRAG_FLAG_GLOBAL "
+                    + "and DRAG_FLAG_GLOBAL_SAME_APPLICATION, the drag will default to "
+                    + "DRAG_FLAG_GLOBAL_SAME_APPLICATION");
+            flags &= ~DRAG_FLAG_GLOBAL;
+        }
 
         if (data != null) {
-            data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0);
+            if (com.android.window.flags.Flags.delegateUnhandledDrags()) {
+                data.prepareToLeaveProcess(
+                        (flags & (DRAG_FLAG_GLOBAL_SAME_APPLICATION | DRAG_FLAG_GLOBAL)) != 0);
+                if ((flags & DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG) != 0) {
+                    if (!data.hasActivityPendingIntents()) {
+                        // Reset the flag if there is no launchable activity intent
+                        flags &= ~DRAG_FLAG_START_PENDING_INTENT_ON_UNHANDLED_DRAG;
+                        Log.w(VIEW_LOG_TAG, "startDragAndDrop called with "
+                                + "DRAG_FLAG_START_INTENT_ON_UNHANDLED_DRAG but the clip data "
+                                + "contains non-activity PendingIntents");
+                    }
+                }
+            } else {
+                data.prepareToLeaveProcess((flags & DRAG_FLAG_GLOBAL) != 0);
+            }
         }
 
         Rect bounds = new Rect();
@@ -28524,6 +28580,7 @@
                 if (token != null) {
                     root.setLocalDragState(myLocalState);
                     mAttachInfo.mDragToken = token;
+                    mAttachInfo.mDragData = data;
                     mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this);
                     setAccessibilityDragStarted(true);
                 }
@@ -28601,8 +28658,12 @@
                 if (mAttachInfo.mDragSurface != null) {
                     mAttachInfo.mDragSurface.release();
                 }
+                if (mAttachInfo.mDragData != null) {
+                    mAttachInfo.mDragData.cleanUpPendingIntents();
+                }
                 mAttachInfo.mDragSurface = surface;
                 mAttachInfo.mDragToken = token;
+                mAttachInfo.mDragData = data;
                 // Cache the local state object for delivery with DragEvents
                 root.setLocalDragState(myLocalState);
                 if (a11yEnabled) {
@@ -31516,11 +31577,15 @@
         IBinder mDragToken;
 
         /**
+         * Used to track the data of the current drag operation for cleanup later.
+         */
+        ClipData mDragData;
+
+        /**
          * The drag shadow surface for the current drag operation.
          */
         public Surface mDragSurface;
 
-
         /**
          * The view that currently has a tooltip displayed.
          */
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 07c9795..28a7334 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -8599,6 +8599,10 @@
                         mAttachInfo.mDragSurface.release();
                         mAttachInfo.mDragSurface = null;
                     }
+                    if (mAttachInfo.mDragData != null) {
+                        mAttachInfo.mDragData.cleanUpPendingIntents();
+                        mAttachInfo.mDragData = null;
+                    }
                 }
             }
         }
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 921afaa..fbebe1e 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -34,6 +34,7 @@
 import static android.view.WindowInsets.Type.indexOf;
 import static android.view.WindowInsets.Type.systemBars;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -44,8 +45,10 @@
 import android.content.Intent;
 import android.graphics.Insets;
 import android.graphics.Rect;
+import android.util.Size;
 import android.view.View.OnApplyWindowInsetsListener;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.flags.Flags;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethod;
 
@@ -54,7 +57,10 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -78,6 +84,8 @@
     private final Insets[] mTypeInsetsMap;
     private final Insets[] mTypeMaxInsetsMap;
     private final boolean[] mTypeVisibilityMap;
+    private final Rect[][] mTypeBoundingRectsMap;
+    private final Rect[][] mTypeMaxBoundingRectsMap;
 
     @Nullable private Rect mTempRect;
     private final boolean mIsRound;
@@ -85,6 +93,8 @@
     @Nullable private final RoundedCorners mRoundedCorners;
     @Nullable private final PrivacyIndicatorBounds mPrivacyIndicatorBounds;
     @Nullable private final DisplayShape mDisplayShape;
+    private final int mFrameWidth;
+    private final int mFrameHeight;
 
     private final @InsetsType int mForceConsumingTypes;
     private final @InsetsType int mSuppressScrimTypes;
@@ -114,7 +124,7 @@
     static {
         CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null),
                 createCompatVisibilityMap(createCompatTypeMap(null)), false, 0, 0, null,
-                null, null, null, systemBars(), false);
+                null, null, null, systemBars(), false, null, null, 0, 0);
     }
 
     /**
@@ -139,7 +149,10 @@
             RoundedCorners roundedCorners,
             PrivacyIndicatorBounds privacyIndicatorBounds,
             DisplayShape displayShape,
-            @InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility) {
+            @InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility,
+            Rect[][] typeBoundingRectsMap,
+            Rect[][] typeMaxBoundingRectsMap,
+            int frameWidth, int frameHeight) {
         mSystemWindowInsetsConsumed = typeInsetsMap == null;
         mTypeInsetsMap = mSystemWindowInsetsConsumed
                 ? new Insets[SIZE]
@@ -164,6 +177,14 @@
         mRoundedCorners = roundedCorners;
         mPrivacyIndicatorBounds = privacyIndicatorBounds;
         mDisplayShape = displayShape;
+        mTypeBoundingRectsMap = (mSystemWindowInsetsConsumed || typeBoundingRectsMap == null)
+                ? new Rect[SIZE][]
+                : typeBoundingRectsMap.clone();
+        mTypeMaxBoundingRectsMap = (mStableInsetsConsumed || typeMaxBoundingRectsMap == null)
+                ? new Rect[SIZE][]
+                : typeMaxBoundingRectsMap.clone();
+        mFrameWidth = frameWidth;
+        mFrameHeight = frameHeight;
     }
 
     /**
@@ -181,7 +202,11 @@
                 src.mPrivacyIndicatorBounds,
                 src.mDisplayShape,
                 src.mCompatInsetsTypes,
-                src.mCompatIgnoreVisibility);
+                src.mCompatIgnoreVisibility,
+                src.mSystemWindowInsetsConsumed ? null : src.mTypeBoundingRectsMap,
+                src.mStableInsetsConsumed ? null : src.mTypeMaxBoundingRectsMap,
+                src.mFrameWidth,
+                src.mFrameHeight);
     }
 
     private static DisplayCutout displayCutoutCopyConstructorArgument(WindowInsets w) {
@@ -233,7 +258,8 @@
     @UnsupportedAppUsage
     public WindowInsets(Rect systemWindowInsets) {
         this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, 0, 0,
-                null, null, null, null, systemBars(), false /* compatIgnoreVisibility */);
+                null, null, null, null, systemBars(), false /* compatIgnoreVisibility */,
+                new Rect[SIZE][], null, 0, 0);
     }
 
     /**
@@ -475,6 +501,111 @@
     }
 
     /**
+     * Returns a list of {@link Rect}s, each of which is the bounding rectangle for an area
+     * that is being partially or fully obscured inside the window.
+     *
+     * <p>
+     * May be used with or instead of {@link Insets} for finer avoidance of regions that may be
+     * partially obscuring the window but may be smaller than those provided by
+     * {@link #getInsets(int)}.
+     * </p>
+     *
+     * <p>
+     * The {@link Rect}s returned are always cropped to the bounds of the window frame and their
+     * coordinate values are relative to the {@link #getFrame()}, regardless of the window's
+     * position on screen.
+     * </p>
+     *
+     * <p>
+     * If inset by {@link #inset(Insets)}, bounding rects that intersect with the provided insets
+     * will be resized to only include the intersection with the remaining frame. Bounding rects
+     * may be completely removed if they no longer intersect with the new instance.
+     * </p>
+     *
+     * @param typeMask the insets type for which to obtain the bounding rectangles
+     * @return the bounding rectangles
+     */
+    @FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS)
+    @NonNull
+    public List<Rect> getBoundingRects(@InsetsType int typeMask) {
+        Rect[] allRects = null;
+        for (int i = FIRST; i <= LAST; i = i << 1) {
+            if ((typeMask & i) == 0) {
+                continue;
+            }
+            final Rect[] rects = mTypeBoundingRectsMap[indexOf(i)];
+            if (rects == null) {
+                continue;
+            }
+            if (allRects == null) {
+                allRects = rects;
+            } else {
+                final Rect[] concat = new Rect[allRects.length + rects.length];
+                System.arraycopy(allRects, 0, concat, 0, allRects.length);
+                System.arraycopy(rects, 0, concat, allRects.length, rects.length);
+                allRects = concat;
+            }
+        }
+        if (allRects == null) {
+            return Collections.emptyList();
+        }
+        return Arrays.asList(allRects);
+    }
+
+    /**
+     * Returns a list of {@link Rect}s, each of which is the bounding rectangle for an area that
+     * can be partially or fully obscured inside the window, regardless of whether
+     * that type is currently visible or not.
+     *
+     * <p> The bounding rects represent areas of a window that <b>may</b> be partially or fully
+     * obscured by the {@code type}. This value does not change based on the visibility state of
+     * those elements. For example, if the status bar is normally shown, but temporarily hidden,
+     * the bounding rects returned here will provide the rects associated with the status bar being
+     * shown.</p>
+     *
+     * <p>
+     * May be used with or instead of {@link Insets} for finer avoidance of regions that may be
+     * partially obscuring the window but may be smaller than those provided by
+     * {@link #getInsetsIgnoringVisibility(int)}.
+     * </p>
+     *
+     * <p>
+     * The {@link Rect}s returned are always cropped to the bounds of the window frame and their
+     * coordinate values are relative to the {@link #getFrame()}, regardless of the window's
+     * position on screen.
+     * </p>
+     *
+     * @param typeMask the insets type for which to obtain the bounding rectangles
+     * @return the bounding rectangles
+     */
+    @FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS)
+    @NonNull
+    public List<Rect> getBoundingRectsIgnoringVisibility(@InsetsType int typeMask) {
+        Rect[] allRects = null;
+        for (int i = FIRST; i <= LAST; i = i << 1) {
+            if ((typeMask & i) == 0) {
+                continue;
+            }
+            final Rect[] rects = mTypeMaxBoundingRectsMap[indexOf(i)];
+            if (rects == null) {
+                continue;
+            }
+            if (allRects == null) {
+                allRects = rects;
+            } else {
+                final Rect[] concat = new Rect[allRects.length + rects.length];
+                System.arraycopy(allRects, 0, concat, 0, allRects.length);
+                System.arraycopy(rects, 0, concat, allRects.length, rects.length);
+                allRects = concat;
+            }
+        }
+        if (allRects == null) {
+            return Collections.emptyList();
+        }
+        return Arrays.asList(allRects);
+    }
+
+    /**
      * Returns the display cutout if there is one.
      *
      * <p>Note: the display cutout will already be {@link #consumeDisplayCutout consumed} during
@@ -555,7 +686,10 @@
                 mTypeVisibilityMap,
                 mIsRound, mForceConsumingTypes, mSuppressScrimTypes,
                 null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape,
-                mCompatInsetsTypes, mCompatIgnoreVisibility);
+                mCompatInsetsTypes, mCompatIgnoreVisibility,
+                mSystemWindowInsetsConsumed ? null : mTypeBoundingRectsMap,
+                mStableInsetsConsumed ? null : mTypeMaxBoundingRectsMap,
+                mFrameWidth, mFrameHeight);
     }
 
 
@@ -610,7 +744,7 @@
                 (mCompatInsetsTypes & displayCutout()) != 0
                         ? null : displayCutoutCopyConstructorArgument(this),
                 mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes,
-                mCompatIgnoreVisibility);
+                mCompatIgnoreVisibility, null, null, mFrameWidth, mFrameHeight);
     }
 
     // TODO(b/119190588): replace @code with @link below
@@ -914,6 +1048,10 @@
                 result.append(Type.toString(1 << i)).append("=").append(insets)
                         .append(" max=").append(maxInsets)
                         .append(" vis=").append(visible)
+                        .append(" boundingRects=")
+                        .append(Arrays.toString(mTypeBoundingRectsMap[i]))
+                        .append(" maxBoundingRects=")
+                        .append(Arrays.toString(mTypeMaxBoundingRectsMap[i]))
                         .append("\n    ");
             }
         }
@@ -942,6 +1080,10 @@
         result.append("displayCutoutConsumed=" + mDisplayCutoutConsumed);
         result.append("\n    ");
         result.append(isRound() ? "round" : "");
+        result.append("\n    ");
+        result.append("frameWidth=" + mFrameWidth);
+        result.append("\n    ");
+        result.append("frameHeight=" + mFrameHeight);
         result.append("}");
         return result.toString();
     }
@@ -1013,6 +1155,27 @@
     }
 
     /**
+     * Returns the assumed size of the window, relative to which the {@link #getInsets} and
+     * {@link #getBoundingRects} have been calculated.
+     *
+     * <p> May be used with {@link #getBoundingRects} to better understand their position within
+     * the window, such as the area between the edge of a bounding rect and the edge of the window.
+     *
+     * <p>Note: the size may not match the actual size of the window, which is determined during
+     * the layout pass - as {@link WindowInsets} are dispatched before layout.
+     *
+     * <p>Caution: using this value in determining the actual window size may make the result of
+     * layout passes unstable and should be avoided.
+     *
+     * @return the assumed size of the window during the inset calculation
+     */
+    @FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS)
+    @NonNull
+    public Size getFrame() {
+        return new Size(mFrameWidth, mFrameHeight);
+    }
+
+    /**
      * @see #inset(int, int, int, int)
      * @hide
      */
@@ -1039,7 +1202,17 @@
                         ? null
                         : mPrivacyIndicatorBounds.inset(left, top, right, bottom),
                 mDisplayShape,
-                mCompatInsetsTypes, mCompatIgnoreVisibility);
+                mCompatInsetsTypes, mCompatIgnoreVisibility,
+                mSystemWindowInsetsConsumed
+                        ? null
+                        : insetBoundingRects(mTypeBoundingRectsMap, left, top, right, bottom,
+                                mFrameWidth, mFrameHeight),
+                mStableInsetsConsumed
+                        ? null
+                        : insetBoundingRects(mTypeMaxBoundingRectsMap, left, top, right, bottom,
+                                mFrameWidth, mFrameHeight),
+                Math.max(0, mFrameWidth - left - right),
+                Math.max(0, mFrameHeight - top - bottom));
     }
 
     @Override
@@ -1060,7 +1233,11 @@
                 && Objects.equals(mDisplayCutout, that.mDisplayCutout)
                 && Objects.equals(mRoundedCorners, that.mRoundedCorners)
                 && Objects.equals(mPrivacyIndicatorBounds, that.mPrivacyIndicatorBounds)
-                && Objects.equals(mDisplayShape, that.mDisplayShape);
+                && Objects.equals(mDisplayShape, that.mDisplayShape)
+                && Arrays.deepEquals(mTypeBoundingRectsMap, that.mTypeBoundingRectsMap)
+                && Arrays.deepEquals(mTypeMaxBoundingRectsMap, that.mTypeMaxBoundingRectsMap)
+                && mFrameWidth == that.mFrameWidth
+                && mFrameHeight == that.mFrameHeight;
     }
 
     @Override
@@ -1069,7 +1246,8 @@
                 Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners,
                 mForceConsumingTypes, mSuppressScrimTypes, mSystemWindowInsetsConsumed,
                 mStableInsetsConsumed, mDisplayCutoutConsumed, mPrivacyIndicatorBounds,
-                mDisplayShape);
+                mDisplayShape, Arrays.deepHashCode(mTypeBoundingRectsMap),
+                Arrays.deepHashCode(mTypeMaxBoundingRectsMap), mFrameWidth, mFrameHeight);
     }
 
 
@@ -1110,6 +1288,68 @@
         return Insets.of(newLeft, newTop, newRight, newBottom);
     }
 
+    static Rect[][] insetBoundingRects(Rect[][] typeBoundingRectsMap,
+            int insetLeft, int insetTop, int insetRight, int insetBottom, int frameWidth,
+            int frameHeight) {
+        if (insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) {
+            return typeBoundingRectsMap;
+        }
+        boolean cloned = false;
+        for (int i = 0; i < SIZE; i++) {
+            final Rect[] boundingRects = typeBoundingRectsMap[i];
+            if (boundingRects == null) {
+                continue;
+            }
+            final Rect[] insetBoundingRects = insetBoundingRects(boundingRects,
+                    insetLeft, insetTop, insetRight, insetBottom, frameWidth, frameHeight);
+            if (!Arrays.equals(insetBoundingRects, boundingRects)) {
+                if (!cloned) {
+                    typeBoundingRectsMap = typeBoundingRectsMap.clone();
+                    cloned = true;
+                }
+                typeBoundingRectsMap[i] = insetBoundingRects;
+            }
+        }
+        return typeBoundingRectsMap;
+    }
+
+    static Rect[] insetBoundingRects(Rect[] boundingRects,
+            int left, int top, int right, int bottom, int frameWidth, int frameHeight) {
+        final List<Rect> insetBoundingRectsList = new ArrayList<>();
+        for (int i = 0; i < boundingRects.length; i++) {
+            final Rect insetRect = insetRect(boundingRects[i], left, top, right, bottom,
+                    frameWidth, frameHeight);
+            if (insetRect != null) {
+                insetBoundingRectsList.add(insetRect);
+            }
+        }
+        return insetBoundingRectsList.toArray(new Rect[0]);
+    }
+
+    private static Rect insetRect(Rect orig, int insetLeft, int insetTop, int insetRight,
+            int insetBottom, int frameWidth, int frameHeight) {
+        if (orig == null) {
+            return null;
+        }
+
+        // Calculate the inset frame, and leave it in that coordinate space for easier comparison
+        // against the |orig| rect.
+        final Rect insetFrame = new Rect(insetLeft, insetTop, frameWidth - insetRight,
+                frameHeight - insetBottom);
+        // Then the intersecting portion of |orig| with the inset |insetFrame|.
+        final Rect insetRect = new Rect();
+        if (insetRect.setIntersect(insetFrame, orig)) {
+            // The intersection is the inset rect, but its position must be shifted to be relative
+            // to the frame. Since the new frame will start at left=|insetLeft| and top=|insetTop|,
+            // just offset that much back in the direction of the origin of the frame.
+            insetRect.offset(-insetLeft, -insetTop);
+            return insetRect;
+        } else {
+            // The |orig| rect does not intersect with the new frame at all, so don't report it.
+            return null;
+        }
+    }
+
     /**
      * @return whether system window insets have been consumed.
      */
@@ -1125,6 +1365,8 @@
         private final Insets[] mTypeInsetsMap;
         private final Insets[] mTypeMaxInsetsMap;
         private final boolean[] mTypeVisibilityMap;
+        private final Rect[][] mTypeBoundingRectsMap;
+        private final Rect[][] mTypeMaxBoundingRectsMap;
         private boolean mSystemInsetsConsumed = true;
         private boolean mStableInsetsConsumed = true;
 
@@ -1137,6 +1379,8 @@
         private @InsetsType int mSuppressScrimTypes;
 
         private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds();
+        private int mFrameWidth;
+        private int mFrameHeight;
 
         /**
          * Creates a builder where all insets are initially consumed.
@@ -1145,6 +1389,8 @@
             mTypeInsetsMap = new Insets[SIZE];
             mTypeMaxInsetsMap = new Insets[SIZE];
             mTypeVisibilityMap = new boolean[SIZE];
+            mTypeBoundingRectsMap = new Rect[SIZE][];
+            mTypeMaxBoundingRectsMap = new Rect[SIZE][];
         }
 
         /**
@@ -1165,6 +1411,10 @@
             mSuppressScrimTypes = insets.mSuppressScrimTypes;
             mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds;
             mDisplayShape = insets.mDisplayShape;
+            mTypeBoundingRectsMap = insets.mTypeBoundingRectsMap.clone();
+            mTypeMaxBoundingRectsMap = insets.mTypeMaxBoundingRectsMap.clone();
+            mFrameWidth = insets.mFrameWidth;
+            mFrameHeight = insets.mFrameHeight;
         }
 
         /**
@@ -1452,6 +1702,68 @@
         }
 
         /**
+         * Sets the bounding rects.
+         *
+         * @param typeMask the inset types to which these rects apply.
+         * @param rects the bounding rects.
+         * @return itself.
+         */
+        @FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS)
+        @NonNull
+        public Builder setBoundingRects(@InsetsType int typeMask, @NonNull List<Rect> rects) {
+            for (int i = FIRST; i <= LAST; i = i << 1) {
+                if ((typeMask & i) == 0) {
+                    continue;
+                }
+                mTypeBoundingRectsMap[indexOf(i)] = rects.toArray(new Rect[0]);
+            }
+            return this;
+        }
+
+        /**
+         * Sets the bounding rects while ignoring their visibility state.
+         *
+         * @param typeMask the inset types to which these rects apply.
+         * @param rects the bounding rects.
+         * @return itself.
+         *
+         * @throws IllegalArgumentException If {@code typeMask} contains {@link Type#ime()}.
+         * Maximum bounding rects are not available for this type as the height of the IME is
+         * dynamic depending on the {@link EditorInfo} of the currently focused view, as well as
+         * the UI state of the IME.
+         */
+        @FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS)
+        @NonNull
+        public Builder setBoundingRectsIgnoringVisibility(@InsetsType int typeMask,
+                @NonNull List<Rect> rects) {
+            if (typeMask == IME) {
+                throw new IllegalArgumentException("Maximum bounding rects not available for IME");
+            }
+            for (int i = FIRST; i <= LAST; i = i << 1) {
+                if ((typeMask & i) == 0) {
+                    continue;
+                }
+                mTypeMaxBoundingRectsMap[indexOf(i)] = rects.toArray(new Rect[0]);
+            }
+            return this;
+        }
+
+        /**
+         * Set the frame size.
+         *
+         * @param width the width of the frame.
+         * @param height the height of the frame.
+         * @return itself.
+         */
+        @FlaggedApi(Flags.FLAG_CUSTOMIZABLE_WINDOW_HEADERS)
+        @NonNull
+        public Builder setFrame(int width, int height) {
+            mFrameWidth = width;
+            mFrameHeight = height;
+            return this;
+        }
+
+        /**
          * Builds a {@link WindowInsets} instance.
          *
          * @return the {@link WindowInsets} instance.
@@ -1462,7 +1774,10 @@
                     mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
                     mIsRound, mForceConsumingTypes, mSuppressScrimTypes, mDisplayCutout,
                     mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, systemBars(),
-                    false /* compatIgnoreVisibility */);
+                    false /* compatIgnoreVisibility */,
+                    mSystemInsetsConsumed ? null : mTypeBoundingRectsMap,
+                    mStableInsetsConsumed ? null : mTypeMaxBoundingRectsMap,
+                    mFrameWidth, mFrameHeight);
         }
     }
 
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index ae00b70..2fb5213 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -520,11 +520,16 @@
     public void registerTrustedPresentationListener(@NonNull IBinder window,
             @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor,
             @NonNull Consumer<Boolean> listener) {
+        Objects.requireNonNull(window, "window must not be null");
+        Objects.requireNonNull(thresholds, "thresholds must not be null");
+        Objects.requireNonNull(executor, "executor must not be null");
+        Objects.requireNonNull(listener, "listener must not be null");
         mGlobal.registerTrustedPresentationListener(window, thresholds, executor, listener);
     }
 
     @Override
     public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) {
+        Objects.requireNonNull(listener, "listener must not be null");
         mGlobal.unregisterTrustedPresentationListener(listener);
     }
 
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index 89da041..474c61f 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -530,13 +530,14 @@
 
     @AnyThread
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
-    static boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
+    static boolean isStylusHandwritingAvailableAsUser(
+            @UserIdInt int userId, boolean connectionless) {
         final IInputMethodManager service = getService();
         if (service == null) {
             return false;
         }
         try {
-            return service.isStylusHandwritingAvailableAsUser(userId);
+            return service.isStylusHandwritingAvailableAsUser(userId, connectionless);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index b60efc1..7c9678f 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -204,6 +204,9 @@
      */
     private final boolean mSupportsStylusHandwriting;
 
+    /** The flag whether this IME supports connectionless stylus handwriting sessions. */
+    private final boolean mSupportsConnectionlessStylusHandwriting;
+
     /**
      * The stylus handwriting setting activity's name, used by the system settings to
      * launch the stylus handwriting specific setting activity of this input method.
@@ -330,6 +333,9 @@
                     com.android.internal.R.styleable.InputMethod_configChanges, 0);
             mSupportsStylusHandwriting = sa.getBoolean(
                     com.android.internal.R.styleable.InputMethod_supportsStylusHandwriting, false);
+            mSupportsConnectionlessStylusHandwriting = sa.getBoolean(
+                    com.android.internal.R.styleable
+                            .InputMethod_supportsConnectionlessStylusHandwriting, false);
             stylusHandwritingSettingsActivity = sa.getString(
                     com.android.internal.R.styleable.InputMethod_stylusHandwritingSettingsActivity);
             sa.recycle();
@@ -442,6 +448,7 @@
         mSubtypes = source.mSubtypes;
         mHandledConfigChanges = source.mHandledConfigChanges;
         mSupportsStylusHandwriting = source.mSupportsStylusHandwriting;
+        mSupportsConnectionlessStylusHandwriting = source.mSupportsConnectionlessStylusHandwriting;
         mForceDefault = source.mForceDefault;
         mStylusHandwritingSettingsActivityAttr = source.mStylusHandwritingSettingsActivityAttr;
     }
@@ -463,6 +470,7 @@
         mSubtypes = new InputMethodSubtypeArray(source);
         mHandledConfigChanges = source.readInt();
         mSupportsStylusHandwriting = source.readBoolean();
+        mSupportsConnectionlessStylusHandwriting = source.readBoolean();
         mStylusHandwritingSettingsActivityAttr = source.readString8();
         mForceDefault = false;
     }
@@ -479,6 +487,7 @@
                 false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
                 false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */,
                 false /* supportsStylusHandwriting */,
+                false /* supportConnectionlessStylusHandwriting */,
                 null /* stylusHandwritingSettingsActivityAttr */,
                 false /* inlineSuggestionsEnabled */);
     }
@@ -488,9 +497,11 @@
      * @hide
      */
     @TestApi
+    @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING)
     public InputMethodInfo(@NonNull String packageName, @NonNull String className,
             @NonNull CharSequence label, @NonNull String settingsActivity,
             @NonNull String languageSettingsActivity, boolean supportStylusHandwriting,
+            boolean supportConnectionlessStylusHandwriting,
             @NonNull String stylusHandwritingSettingsActivityAttr) {
         this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
                 settingsActivity, languageSettingsActivity, null /* subtypes */,
@@ -498,8 +509,8 @@
                 true /* supportsSwitchingToNextInputMethod */,
                 false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
                 false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */,
-                supportStylusHandwriting, stylusHandwritingSettingsActivityAttr,
-                false /* inlineSuggestionsEnabled */);
+                supportStylusHandwriting, supportConnectionlessStylusHandwriting,
+                stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */);
     }
 
     /**
@@ -517,6 +528,7 @@
                 false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
                 false /* isVirtualDeviceOnly */, handledConfigChanges,
                 false /* supportsStylusHandwriting */,
+                false /* supportConnectionlessStylusHandwriting */,
                 null /* stylusHandwritingSettingsActivityAttr */,
                 false /* inlineSuggestionsEnabled */);
     }
@@ -533,6 +545,7 @@
                 true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */,
                 false /* isVrOnly */, false /* isVirtualDeviceOnly */, 0 /* handledconfigChanges */,
                 false /* supportsStylusHandwriting */,
+                false /* supportConnectionlessStylusHandwriting */,
                 null /* stylusHandwritingSettingsActivityAttr */,
                 false /* inlineSuggestionsEnabled */);
     }
@@ -549,6 +562,7 @@
                 supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly,
                 false /* isVirtualDeviceOnly */,
                 0 /* handledConfigChanges */, false /* supportsStylusHandwriting */,
+                false /* supportConnectionlessStylusHandwriting */,
                 null /* stylusHandwritingSettingsActivityAttr */,
                 false /* inlineSuggestionsEnabled */);
     }
@@ -562,7 +576,8 @@
             int isDefaultResId, boolean forceDefault,
             boolean supportsSwitchingToNextInputMethod, boolean inlineSuggestionsEnabled,
             boolean isVrOnly, boolean isVirtualDeviceOnly, int handledConfigChanges,
-            boolean supportsStylusHandwriting, String stylusHandwritingSettingsActivityAttr,
+            boolean supportsStylusHandwriting, boolean supportsConnectionlessStylusHandwriting,
+            String stylusHandwritingSettingsActivityAttr,
             boolean supportsInlineSuggestionsWithTouchExploration) {
         final ServiceInfo si = ri.serviceInfo;
         mService = ri;
@@ -583,6 +598,7 @@
         mIsVirtualDeviceOnly = isVirtualDeviceOnly;
         mHandledConfigChanges = handledConfigChanges;
         mSupportsStylusHandwriting = supportsStylusHandwriting;
+        mSupportsConnectionlessStylusHandwriting = supportsConnectionlessStylusHandwriting;
         mStylusHandwritingSettingsActivityAttr = stylusHandwritingSettingsActivityAttr;
     }
 
@@ -763,6 +779,16 @@
     }
 
     /**
+     * Returns whether the IME supports connectionless stylus handwriting sessions.
+     *
+     * @attr ref android.R.styleable#InputMethod_supportsConnectionlessStylusHandwriting
+     */
+    @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING)
+    public boolean supportsConnectionlessStylusHandwriting() {
+        return mSupportsConnectionlessStylusHandwriting;
+    }
+
+    /**
      * Returns {@link Intent} for stylus handwriting settings activity with
      * {@link Intent#getAction() Intent action} {@link #ACTION_STYLUS_HANDWRITING_SETTINGS}
      * if IME {@link #supportsStylusHandwriting() supports stylus handwriting}, else
@@ -828,6 +854,8 @@
                 + " mSuppressesSpellChecker=" + mSuppressesSpellChecker
                 + " mShowInInputMethodPicker=" + mShowInInputMethodPicker
                 + " mSupportsStylusHandwriting=" + mSupportsStylusHandwriting
+                + " mSupportsConnectionlessStylusHandwriting="
+                + mSupportsConnectionlessStylusHandwriting
                 + " mStylusHandwritingSettingsActivityAttr="
                         + mStylusHandwritingSettingsActivityAttr);
         pw.println(prefix + "mIsDefaultResId=0x"
@@ -947,6 +975,7 @@
         mSubtypes.writeToParcel(dest);
         dest.writeInt(mHandledConfigChanges);
         dest.writeBoolean(mSupportsStylusHandwriting);
+        dest.writeBoolean(mSupportsConnectionlessStylusHandwriting);
         dest.writeString8(mStylusHandwritingSettingsActivityAttr);
     }
 
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 3d70c5b..6772efb 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -18,6 +18,7 @@
 
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
+import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
 import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE;
 import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
@@ -565,8 +566,15 @@
     @GuardedBy("mH")
     private PropertyInvalidatedCache<Integer, Boolean> mStylusHandwritingAvailableCache;
 
+    /** Cached value for {@link #isConnectionlessStylusHandwritingAvailable} for userId. */
+    @GuardedBy("mH")
+    private PropertyInvalidatedCache<Integer, Boolean>
+            mConnectionlessStylusHandwritingAvailableCache;
+
     private static final String CACHE_KEY_STYLUS_HANDWRITING_PROPERTY =
             "cache_key.system_server.stylus_handwriting";
+    private static final String CACHE_KEY_CONNECTIONLESS_STYLUS_HANDWRITING_PROPERTY =
+            "cache_key.system_server.connectionless_stylus_handwriting";
 
     @GuardedBy("mH")
     private int mCursorSelStart;
@@ -690,6 +698,17 @@
         PropertyInvalidatedCache.invalidateCache(CACHE_KEY_STYLUS_HANDWRITING_PROPERTY);
     }
 
+    /**
+     * Calling this will invalidate the local connectionless stylus handwriting availability cache,
+     * which forces the next query in any process to recompute the cache.
+     *
+     * @hide
+     */
+    public static void invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches() {
+        PropertyInvalidatedCache.invalidateCache(
+                CACHE_KEY_CONNECTIONLESS_STYLUS_HANDWRITING_PROPERTY);
+    }
+
     private static boolean isAutofillUIShowing(View servedView) {
         AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class);
         return afm != null && afm.isAutofillUiShowing();
@@ -1583,7 +1602,7 @@
                     @Override
                     public Boolean recompute(Integer userId) {
                         return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(
-                                userId);
+                                userId, /* connectionless= */ false);
                     }
                 };
             }
@@ -1593,6 +1612,30 @@
     }
 
     /**
+     * Returns {@code true} if the currently selected IME supports connectionless stylus handwriting
+     * sessions and is enabled.
+     */
+    @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING)
+    public boolean isConnectionlessStylusHandwritingAvailable() {
+        if (ActivityThread.currentApplication() == null) {
+            return false;
+        }
+        synchronized (mH) {
+            if (mConnectionlessStylusHandwritingAvailableCache == null) {
+                mConnectionlessStylusHandwritingAvailableCache = new PropertyInvalidatedCache<>(
+                        /* maxEntries= */ 4, CACHE_KEY_CONNECTIONLESS_STYLUS_HANDWRITING_PROPERTY) {
+                    @Override
+                    public Boolean recompute(@NonNull Integer userId) {
+                        return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(
+                                userId, /* connectionless= */ true);
+                    }
+                };
+            }
+            return mConnectionlessStylusHandwritingAvailableCache.query(UserHandle.myUserId());
+        }
+    }
+
+    /**
      * Returns the list of installed input methods for the specified user.
      *
      * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
@@ -1950,6 +1993,10 @@
         if (mServedView != null) {
             clearedView = mServedView;
             mServedView = null;
+            if (initiationWithoutInputConnection() && clearedView.getViewRootImpl() != null) {
+                clearedView.getViewRootImpl().getHandwritingInitiator()
+                        .clearFocusedView(clearedView);
+            }
         }
         if (clearedView != null) {
             if (DEBUG) {
@@ -2932,6 +2979,10 @@
             switch (res.result) {
                 case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
                     mRestartOnNextWindowFocus = true;
+                    if (initiationWithoutInputConnection()) {
+                        mServedView.getViewRootImpl().getHandwritingInitiator().clearFocusedView(
+                                mServedView);
+                    }
                     mServedView = null;
                     break;
             }
@@ -3094,6 +3145,11 @@
             return false;
         }
         mServedView = mNextServedView;
+        if (initiationWithoutInputConnection() && mServedView.onCheckIsTextEditor()
+                && mServedView.isHandwritingDelegate()) {
+            mServedView.getViewRootImpl().getHandwritingInitiator().onDelegateViewFocused(
+                    mServedView);
+        }
         if (mServedInputConnection != null) {
             mServedInputConnection.finishComposingTextFromImm();
         }
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 7f1cc8e..55986e7 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -62,3 +62,19 @@
     bug: "311791923"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "initiation_without_input_connection"
+    namespace: "input_method"
+    description: "Feature flag for initiating handwriting without InputConnection"
+    bug: "308827131"
+    is_fixed_read_only: true
+}
+
+flag {
+    name: "connectionless_handwriting"
+    namespace: "input_method"
+    description: "Feature flag for connectionless stylus handwriting APIs"
+    bug: "300979854"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig
index 9f0b7c3..bfe3d05 100644
--- a/core/java/android/widget/flags/notification_widget_flags.aconfig
+++ b/core/java/android/widget/flags/notification_widget_flags.aconfig
@@ -5,4 +5,14 @@
    namespace: "systemui"
    description: "Enables notification specific LinearLayout optimization"
    bug: "316110233"
+}
+
+flag {
+  name: "call_style_set_data_async"
+  namespace: "systemui"
+  description: "Offloads caller icon drawable loading to the background thread"
+  bug: "293961072"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
 }
\ No newline at end of file
diff --git a/core/java/android/window/IUnhandledDragCallback.aidl b/core/java/android/window/IUnhandledDragCallback.aidl
new file mode 100644
index 0000000..7806b1f
--- /dev/null
+++ b/core/java/android/window/IUnhandledDragCallback.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.view.DragEvent;
+
+/**
+ * A callback for notifying the system when the unhandled drop is complete.
+ * {@hide}
+ */
+oneway interface IUnhandledDragCallback {
+    /**
+     * Called when the IUnhandledDropListener has fully handled the drop, and the drag can be
+     * cleaned up.  If handled is `true`, then cleanup of the drag and drag surface will be
+     * immediate, otherwise, the system will treat the drag as a cancel back to the start of the
+     * drag.
+     */
+    void notifyUnhandledDropComplete(boolean handled);
+}
diff --git a/core/java/android/window/IUnhandledDragListener.aidl b/core/java/android/window/IUnhandledDragListener.aidl
new file mode 100644
index 0000000..52e9895
--- /dev/null
+++ b/core/java/android/window/IUnhandledDragListener.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+import android.view.DragEvent;
+import android.window.IUnhandledDragCallback;
+
+/**
+ * An interface to a handler for global drags that are not consumed (ie. not handled by any window).
+ * {@hide}
+ */
+oneway interface IUnhandledDragListener {
+    /**
+     * Called when the user finishes the drag gesture but no windows have reported handling the
+     * drop.  The DragEvent is populated with the drag surface for the listener to animate.  The
+     * listener *MUST* call the provided callback exactly once when it has finished handling the
+     * drop.  If the listener calls the callback with `true` then it is responsible for removing
+     * and releasing the drag surface passed through the DragEvent.
+     */
+    void onUnhandledDrop(in DragEvent event, in IUnhandledDragCallback callback);
+}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index efc71d7..76a34ae 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -676,18 +676,20 @@
      *                 This identifies them.
      * @param type     The {@link InsetsType} of the insets source.
      * @param frame    The rectangle area of the insets source.
+     * @param boundingRects The bounding rects within this inset, relative to the |frame|.
      * @hide
      */
     @NonNull
     public WindowContainerTransaction addInsetsSource(
             @NonNull WindowContainerToken receiver,
-            IBinder owner, int index, @InsetsType int type, Rect frame) {
+            IBinder owner, int index, @InsetsType int type, Rect frame, Rect[] boundingRects) {
         final HierarchyOp hierarchyOp =
                 new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER)
                         .setContainer(receiver.asBinder())
                         .setInsetsFrameProvider(new InsetsFrameProvider(owner, index, type)
                                 .setSource(InsetsFrameProvider.SOURCE_ARBITRARY_RECTANGLE)
-                                .setArbitraryRectangle(frame))
+                                .setArbitraryRectangle(frame)
+                                .setBoundingRects(boundingRects))
                         .setInsetsFrameOwner(owner)
                         .build();
         mHierarchyOps.add(hierarchyOp);
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 0b7593a..c5c17cf 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -1146,7 +1146,7 @@
             mHistoryCur.batteryHealth = (byte) health;
             mHistoryCur.batteryPlugType = (byte) plugType;
             mHistoryCur.batteryTemperature = (short) temperature;
-            mHistoryCur.batteryVoltage = (char) voltageMv;
+            mHistoryCur.batteryVoltage = (short) voltageMv;
             mHistoryCur.batteryChargeUah = chargeUah;
         }
     }
@@ -2010,7 +2010,11 @@
         int bits = 0;
         bits = setBitField(bits, h.batteryLevel, 25, 0xfe000000 /* 7F << 25 */);
         bits = setBitField(bits, h.batteryTemperature, 15, 0x01ff8000 /* 3FF << 15 */);
-        bits = setBitField(bits, h.batteryVoltage, 1, 0x00007ffe /* 3FFF << 1 */);
+        short voltage = (short) h.batteryVoltage;
+        if (voltage == -1) {
+            voltage = 0x3FFF;
+        }
+        bits = setBitField(bits, voltage, 1, 0x00007ffe /* 3FFF << 1 */);
         return bits;
     }
 
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index 739ee48..b2a6a93 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -309,7 +309,12 @@
     private static void readBatteryLevelInt(int batteryLevelInt, BatteryStats.HistoryItem out) {
         out.batteryLevel = (byte) ((batteryLevelInt & 0xfe000000) >>> 25);
         out.batteryTemperature = (short) ((batteryLevelInt & 0x01ff8000) >>> 15);
-        out.batteryVoltage = (char) ((batteryLevelInt & 0x00007ffe) >>> 1);
+        int voltage = ((batteryLevelInt & 0x00007ffe) >>> 1);
+        if (voltage == 0x3FFF) {
+            voltage = -1;
+        }
+
+        out.batteryVoltage = (short) voltage;
     }
 
     /**
diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java
index e9a8d4b..1f4abc1 100644
--- a/core/java/com/android/internal/os/TimeoutRecord.java
+++ b/core/java/com/android/internal/os/TimeoutRecord.java
@@ -45,6 +45,7 @@
             TimeoutKind.APP_REGISTERED,
             TimeoutKind.SHORT_FGS_TIMEOUT,
             TimeoutKind.JOB_SERVICE,
+            TimeoutKind.FGS_TIMEOUT,
     })
 
     @Retention(RetentionPolicy.SOURCE)
@@ -59,6 +60,7 @@
         int SHORT_FGS_TIMEOUT = 8;
         int JOB_SERVICE = 9;
         int APP_START = 10;
+        int FGS_TIMEOUT = 11;
     }
 
     /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */
@@ -186,6 +188,12 @@
         return TimeoutRecord.endingNow(TimeoutKind.SHORT_FGS_TIMEOUT, reason);
     }
 
+    /** Record for a "foreground service" timeout. */
+    @NonNull
+    public static TimeoutRecord forFgsTimeout(String reason) {
+        return TimeoutRecord.endingNow(TimeoutKind.FGS_TIMEOUT, reason);
+    }
+
     /** Record for a job related timeout. */
     @NonNull
     public static TimeoutRecord forJobService(String reason) {
diff --git a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
index f62ff38..e11067d 100644
--- a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
+++ b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
@@ -22,6 +22,7 @@
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__BROADCAST_OF_INTENT;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__CONTENT_PROVIDER_NOT_RESPONDING;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__EXECUTING_SERVICE;
+import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__FGS_TIMEOUT;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT_NO_FOCUSED_WINDOW;
 import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__JOB_SERVICE;
@@ -548,6 +549,8 @@
                 return ANRLATENCY_REPORTED__ANR_TYPE__SHORT_FGS_TIMEOUT;
             case TimeoutKind.JOB_SERVICE:
                 return ANRLATENCY_REPORTED__ANR_TYPE__JOB_SERVICE;
+            case TimeoutKind.FGS_TIMEOUT:
+                return ANRLATENCY_REPORTED__ANR_TYPE__FGS_TIMEOUT;
             default:
                 return ANRLATENCY_REPORTED__ANR_TYPE__UNKNOWN_ANR_TYPE;
         }
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 595bf3b..ca5d441 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -158,7 +158,7 @@
     /** Returns {@code true} if currently selected IME supports Stylus handwriting. */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
-    boolean isStylusHandwritingAvailableAsUser(int userId);
+    boolean isStylusHandwritingAvailableAsUser(int userId, boolean connectionless);
 
     /** add virtual stylus id for test Stylus handwriting session **/
     @EnforcePermission("TEST_INPUT_METHOD")
diff --git a/core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java b/core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java
index b5e9b8f..0ceba25 100644
--- a/core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java
+++ b/core/java/com/android/internal/widget/NotificationOptimizedLinearLayout.java
@@ -53,6 +53,7 @@
  * - LinearLayout doesn't have <code>weightSum</code>.
  * - Horizontal LinearLayout's width should be measured EXACTLY.
  * - Horizontal LinearLayout shouldn't need baseLineAlignment.
+ * - Horizontal LinearLayout shouldn't have any child that has negative left or right margin.
  * - Vertical LinearLayout shouldn't have MATCH_PARENT children when it is not measured EXACTLY.
  *
  * @hide
@@ -88,7 +89,7 @@
         final View weightedChildView = getSingleWeightedChild();
         mShouldUseOptimizedLayout =
                 isUseOptimizedLinearLayoutFlagEnabled() && weightedChildView != null
-                        && isLinearLayoutUsable(widthMeasureSpec, heightMeasureSpec);
+                        && isOptimizationPossible(widthMeasureSpec, heightMeasureSpec);
 
         if (mShouldUseOptimizedLayout) {
             onMeasureOptimized(weightedChildView, widthMeasureSpec, heightMeasureSpec);
@@ -118,7 +119,7 @@
      * @param heightMeasureSpec The height measurement specification.
      * @return `true` if optimization is possible, `false` otherwise.
      */
-    private boolean isLinearLayoutUsable(int widthMeasureSpec, int heightMeasureSpec) {
+    private boolean isOptimizationPossible(int widthMeasureSpec, int heightMeasureSpec) {
         final boolean hasWeightSum = getWeightSum() > 0.0f;
         if (hasWeightSum) {
             logSkipOptimizedOnMeasure("Has weightSum.");
@@ -142,10 +143,36 @@
             logSkipOptimizedOnMeasure("Need to apply baseline.");
             return false;
         }
+
+        if (requiresNegativeMarginHandlingForHorizontalLinearLayout()) {
+            logSkipOptimizedOnMeasure("Need to handle negative margins.");
+            return false;
+        }
         return true;
     }
 
     /**
+     * @return if the horizontal linearlayout requires to handle negative margins in its children.
+     * In that case, we can't use excessSpace because LinearLayout negative margin handling for
+     * excess space and WRAP_CONTENT is different.
+     */
+    private boolean requiresNegativeMarginHandlingForHorizontalLinearLayout() {
+        if (getOrientation() == VERTICAL) {
+            return false;
+        }
+
+        final List<View> activeChildren = getActiveChildren();
+        for (int i = 0; i < activeChildren.size(); i++) {
+            final View child = activeChildren.get(i);
+            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+            if (lp.leftMargin < 0 || lp.rightMargin < 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * @return if the vertical linearlayout requires match_parent children remeasure
      */
     private boolean requiresMatchParentRemeasureForVerticalLinearLayout(int widthMeasureSpec) {
@@ -337,94 +364,81 @@
      */
     private void measureVerticalOptimized(@NonNull View weightedChildView, int widthMeasureSpec,
             int heightMeasureSpec) {
-        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
-        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        int totalLength = 0;
         int maxWidth = 0;
-        int usedHeight = 0;
-        final List<View> activeChildren = getActiveChildren();
-        final int activeChildCount = activeChildren.size();
+        final int availableHeight = MeasureSpec.getSize(heightMeasureSpec);
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
 
-        final boolean isContentFirstItem = !activeChildren.isEmpty() && activeChildren.get(0)
-                == weightedChildView;
-
-        final boolean isContentLastItem = !activeChildren.isEmpty() && activeChildren.get(
-                activeChildCount - 1) == weightedChildView;
-
-        final int horizontalPaddings = getPaddingLeft() + getPaddingRight();
-
-        // 1. Measure other child views.
-        for (int i = 0; i < activeChildCount; i++) {
-            final View child = activeChildren.get(i);
-            if (child == weightedChildView) {
+        // 1. Measure all unweighted children
+        for (int i = 0; i < getChildCount(); i++) {
+            final View child = getChildAt(i);
+            if (child == null || child.getVisibility() == GONE) {
                 continue;
             }
+
             final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
 
-            int requiredVerticalPadding = lp.topMargin + lp.bottomMargin;
-            if (!isContentFirstItem && i == 0) {
-                requiredVerticalPadding += getPaddingTop();
-            }
-            if (!isContentLastItem && i == activeChildCount - 1) {
-                requiredVerticalPadding += getPaddingBottom();
+            if (child == weightedChildView) {
+                // In excessMode, LinearLayout add  weighted child top and bottom margins to
+                // totalLength when their sum is positive.
+                if (lp.height == 0 && heightMode == MeasureSpec.EXACTLY) {
+                    totalLength = Math.max(totalLength, totalLength + lp.topMargin
+                            + lp.bottomMargin);
+                }
+                continue;
             }
 
-            child.measure(ViewGroup.getChildMeasureSpec(widthMeasureSpec,
-                            horizontalPaddings + lp.leftMargin + lp.rightMargin,
-                            child.getLayoutParams().width),
-                    ViewGroup.getChildMeasureSpec(heightMeasureSpec, requiredVerticalPadding,
-                            lp.height));
+            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
+            // LinearLayout only adds measured children heights and its top and bottom margins
+            // to totalLength when their sum is positive.
+            totalLength = Math.max(totalLength,
+                    totalLength + child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
             maxWidth = Math.max(maxWidth,
                     child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
-            usedHeight += child.getMeasuredHeight() + requiredVerticalPadding;
         }
 
-        // measure content
+        // Add padding to totalLength that we are going to use for remaining space.
+        totalLength += mPaddingTop + mPaddingBottom;
+
+        // 2. generate measure spec for weightedChildView.
         final MarginLayoutParams lp = (MarginLayoutParams) weightedChildView.getLayoutParams();
+        // height should be AT_MOST for non EXACT cases.
+        final int childHeightMeasureMode =
+                heightMode == MeasureSpec.EXACTLY ? MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
+        final int childHeightMeasureSpec;
 
-        int usedSpace = usedHeight + lp.topMargin + lp.bottomMargin;
-        if (isContentFirstItem) {
-            usedSpace += getPaddingTop();
+        // In excess mode, LinearLayout measures weighted children with remaining space. Otherwise,
+        // it is measured with remaining space just like other children.
+        if (lp.height == 0 && heightMode == MeasureSpec.EXACTLY) {
+            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+                    Math.max(0, availableHeight - totalLength), childHeightMeasureMode);
+        } else {
+            final int usedHeight = lp.topMargin + lp.bottomMargin + totalLength;
+            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
+                    Math.max(0, availableHeight - usedHeight), childHeightMeasureMode);
         }
-        if (isContentLastItem) {
-            usedSpace += getPaddingBottom();
-        }
+        final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
+                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin, lp.width);
 
-        final int availableWidth = MeasureSpec.getSize(widthMeasureSpec);
-        final int availableHeight = MeasureSpec.getSize(heightMeasureSpec);
-
-        final int childWidthMeasureSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
-                horizontalPaddings + lp.leftMargin + lp.rightMargin, lp.width);
-
-        // 2. Calculate remaining height for weightedChildView.
-        final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
-                Math.max(0, availableHeight - usedSpace), MeasureSpec.AT_MOST);
-
-        // 3. Measure weightedChildView with the remaining remaining space.
+        // 3. Measure weightedChildView with the remaining space.
         weightedChildView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+
+        totalLength = Math.max(totalLength,
+                totalLength + weightedChildView.getMeasuredHeight() + lp.topMargin
+                        + lp.bottomMargin);
+
         maxWidth = Math.max(maxWidth,
                 weightedChildView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
 
-        final int totalUsedHeight = usedSpace + weightedChildView.getMeasuredHeight();
+        // Add padding to width
+        maxWidth += getPaddingLeft() + getPaddingRight();
 
-        final int measuredWidth;
-        if (widthMode == MeasureSpec.EXACTLY) {
-            measuredWidth = availableWidth;
-        } else {
-            measuredWidth = maxWidth + getPaddingStart() + getPaddingEnd();
-        }
-
-        final int measuredHeight;
-        if (heightMode == MeasureSpec.EXACTLY) {
-            measuredHeight = availableHeight;
-        } else {
-            measuredHeight = totalUsedHeight;
-        }
-
-        // 4. Set the container size
-        setMeasuredDimension(
-                resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
-                        widthMeasureSpec),
-                Math.max(getSuggestedMinimumHeight(), measuredHeight));
+        // Resolve final dimensions
+        final int finalWidth = resolveSizeAndState(Math.max(maxWidth, getSuggestedMinimumWidth()),
+                widthMeasureSpec, 0);
+        final int finalHeight = resolveSizeAndState(
+                Math.max(totalLength, getSuggestedMinimumHeight()), heightMeasureSpec, 0);
+        setMeasuredDimension(finalWidth, finalHeight);
     }
 
     @NonNull
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a425bb0..75fbff3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3829,6 +3829,7 @@
         @hide This is not a third-party API (intended for OEMs and system apps). -->
     <permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES"
                 android:protectionLevel="signature|installer" />
+    <uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" />
 
     <!-- @SystemApi @hide Allows an application to set a device owner on retail demo devices.-->
     <permission android:name="android.permission.PROVISION_DEMO_DEVICE"
@@ -5921,7 +5922,7 @@
     <permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS"
         android:protectionLevel="signature|privileged" />
 
-    <!-- Allows an application to collect usage infomation about brightness slider changes.
+    <!-- Allows an application to collect usage information about brightness slider changes.
          <p>Not for use by third-party applications.</p>
          @hide
          @SystemApi
@@ -7033,12 +7034,16 @@
 
     <!-- Allows the holder to read blocked numbers. See
          {@link android.provider.BlockedNumberContract}.
+         @SystemApi
+         @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
          @hide -->
     <permission android:name="android.permission.READ_BLOCKED_NUMBERS"
                 android:protectionLevel="signature" />
 
     <!-- Allows the holder to write blocked numbers. See
          {@link android.provider.BlockedNumberContract}.
+         @SystemApi
+         @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
          @hide -->
     <permission android:name="android.permission.WRITE_BLOCKED_NUMBERS"
                 android:protectionLevel="signature" />
diff --git a/core/res/res/layout/app_perms_summary.xml b/core/res/res/layout/app_perms_summary.xml
index b8d93ac..509b988 100644
--- a/core/res/res/layout/app_perms_summary.xml
+++ b/core/res/res/layout/app_perms_summary.xml
@@ -14,7 +14,7 @@
      limitations under the License.
 -->
 
-<!-- Describes permission item consisting of a group name and the list of permisisons under the group -->
+<!-- Describes permission item consisting of a group name and the list of permissions under the group -->
 
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 41bc825..321f9f9 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3641,6 +3641,18 @@
              <p> The default value is 40dp for {@link android.widget.TextView} and
              {@link android.widget.EditText}, and 0dp for all other views. -->
         <attr name="handwritingBoundsOffsetBottom" format="dimension" />
+
+        <!-- Sets whether this view renders sensitive content. -->
+        <!-- @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") -->
+        <attr name="contentSensitivity">
+            <!-- Let the Android System use its heuristics to determine if the view renders
+             sensitive content. -->
+            <enum name="auto" value="0" />
+            <!-- This view renders sensitive content. -->
+            <enum name="sensitive" value="0x1" />
+            <!-- This view doesn't render sensitive content. -->
+            <enum name="notSensitive" value="0x2" />
+        </attr>
     </declare-styleable>
 
     <!-- Attributes that can be assigned to a tag for a particular View. -->
@@ -3966,6 +3978,26 @@
             {@link android.inputmethodservice.InputMethodService#onFinishInput()}.
         -->
         <attr name="supportsStylusHandwriting" format="boolean" />
+        <!-- Specifies whether the IME supports connectionless stylus handwriting sessions. A
+             connectionless session differs from a regular session in that the IME does not use an
+             input connection to communicate with a text editor. Instead, the IME directly returns
+             recognised handwritten text via an {@link
+             android.inputmethodservice.InputMethodService} handwriting lifecycle API.
+
+             <p>If the IME supports connectionless sessions, apps or framework may start a
+             connectionless session when a stylus motion event sequence begins. {@link
+             android.inputmethodservice.InputMethodService#onStartConnectionlessStylusHandwriting}
+             is called. If the IME is ready for stylus input, it should return {code true} to start
+             the basic mode session. As in the regular session, the IME will receive stylus motion
+             events to the stylus handwriting window and should render ink to a view in this window.
+             When the user has stopped handwriting, the IME should end the session and deliver the
+             result by calling {@link
+             android.inputmethodservice.InputMethodService#finishConnectionlessStylusHandwriting}.
+
+             The default value is {code false}. If {code true}, {@link
+             android.R.attr#supportsStylusHandwriting} should also be {code true}.
+        -->
+        <attr name="supportsConnectionlessStylusHandwriting" format="boolean" />
         <!-- Class name of an activity that allows the user to modify the stylus handwriting
             settings for this service -->
         <attr name="stylusHandwritingSettingsActivity" format="string" />
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index dbe7196..104b7cd 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -58,6 +58,12 @@
     <integer name="auto_data_switch_availability_stability_time_threshold_millis">10000</integer>
     <java-symbol type="integer" name="auto_data_switch_availability_stability_time_threshold_millis" />
 
+    <!-- Define the bar of considering the RAT and signal strength advantage of a subscription to be
+     stable in milliseconds, where 0 means immediate switch, and negative milliseconds indicates the
+     switch base on RAT and signal strength feature is disabled.-->
+    <integer name="auto_data_switch_performance_stability_time_threshold_millis">120000</integer>
+    <java-symbol type="integer" name="auto_data_switch_performance_stability_time_threshold_millis" />
+
     <!-- Define the maximum retry times when a validation for switching failed.-->
     <integer name="auto_data_switch_validation_max_retry">7</integer>
     <java-symbol type="integer" name="auto_data_switch_validation_max_retry" />
@@ -82,6 +88,13 @@
     <bool name="config_wlan_data_service_conn_persistence_on_restart">true</bool>
     <java-symbol type="bool" name="config_wlan_data_service_conn_persistence_on_restart" />
 
+    <!-- Indicating whether the retry timer from setup data call response for data throttling should
+         be honored for emergency network request. By default this is off, meaning for emergency
+         network requests, the data frameworks will ignore the previous retry timer passed in from
+         setup data call response. -->
+    <bool name="config_honor_data_retry_timer_for_emergency_network">false</bool>
+    <java-symbol type="bool" name="config_honor_data_retry_timer_for_emergency_network" />
+
     <!-- Cellular data service package name to bind to by default. If none is specified in an
          overlay, an empty string is passed in -->
     <string name="config_wwan_data_service_package" translatable="false">com.android.phone</string>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 5ee5555..6029d23 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -155,6 +155,10 @@
     <public name="languageSettingsActivity"/>
     <!-- @FlaggedApi("com.android.text.flags.fix_line_height_for_locale") -->
     <public name="useLocalePreferredLineHeightForMinimum"/>
+    <!-- @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") -->
+    <public name="contentSensitivity" />
+    <!-- @FlaggedApi("android.view.inputmethod.connectionless_handwriting") -->
+    <public name="supportsConnectionlessStylusHandwriting" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
index 5aace81..e4cf7ac 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java
@@ -286,7 +286,7 @@
         int scanStatus = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
 
         verify(mTunerMock).seek(/* directionDown= */ true, /* skipSubChannel= */ false);
-        assertWithMessage("Status for scaning")
+        assertWithMessage("Status for scanning")
                 .that(scanStatus).isEqualTo(RadioManager.STATUS_OK);
         verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onProgramInfoChanged(FM_PROGRAM_INFO);
     }
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
index 0806fa0..db95d7a 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerStressTestRunner.java
@@ -99,7 +99,7 @@
         }
     }
 
-    public int getSoftApInterations() {
+    public int getSoftApIterations() {
         return mSoftApIterations;
     }
 
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index e1bcd4a..936f4d7 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -279,5 +279,204 @@
         }
     }
 
+    @Test
+    public void testCalculateBoundingRects_noBoundingRects_createsSingleRect() {
+        mSource.setFrame(new Rect(0, 0, 1000, 100));
+        mSource.setBoundingRects(null);
+
+        final Rect[] rects = mSource.calculateBoundingRects(new Rect(0, 0, 1000, 1000), false);
+
+        assertEquals(1, rects.length);
+        assertEquals(new Rect(0, 0, 1000, 100), rects[0]);
+    }
+
+    @Test
+    public void testCalculateBoundingRects_noBoundingRectsAndLargerFrame_singleRectFitsRelFrame() {
+        mSource.setFrame(new Rect(0, 0, 1000, 100));
+        mSource.setBoundingRects(null);
+
+        final Rect[] rects = mSource.calculateBoundingRects(new Rect(0, 0, 500, 1000), false);
+
+        assertEquals(1, rects.length);
+        assertEquals(new Rect(0, 0, 500, 100), rects[0]);
+    }
+
+    @Test
+    public void testCalculateBoundingRects_frameAtOrigin_resultRelativeToRelFrame() {
+        mSource.setFrame(new Rect(0, 0, 1000, 100));
+        mSource.setBoundingRects(new Rect[]{
+                new Rect(0, 0, 300, 100),
+                new Rect(800, 0, 1000, 100),
+        });
+
+        final Rect[] rects = mSource.calculateBoundingRects(new Rect(0, 0, 1000, 1000), false);
+
+        assertEquals(2, rects.length);
+        assertEquals(new Rect(0, 0, 300, 100), rects[0]);
+        assertEquals(new Rect(800, 0, 1000, 100), rects[1]);
+    }
+
+    @Test
+    public void testCalculateBoundingRects_notAtOrigin_resultRelativeToRelFrame() {
+        mSource.setFrame(new Rect(100, 100, 1100, 200));
+        mSource.setBoundingRects(new Rect[]{
+                new Rect(0, 0, 300, 100),    // 300x100, aligned left
+                new Rect(800, 0, 1000, 100), // 200x100, aligned right
+        });
+
+        final Rect[] rects = mSource.calculateBoundingRects(new Rect(100, 100, 1100, 1100), false);
+
+        assertEquals(2, rects.length);
+        assertEquals(new Rect(0, 0, 300, 100), rects[0]);
+        assertEquals(new Rect(800, 0, 1000, 100), rects[1]);
+    }
+
+    @Test
+    public void testCalculateBoundingRects_boundingRectFullyInsideFrameInWindow() {
+        mSource.setFrame(new Rect(0, 0, 1000, 100));
+        mSource.setBoundingRects(new Rect[]{
+                new Rect(100, 0, 400, 100), // Inside |frame| and |relativeFrame|.
+        });
+
+        final Rect[] rects = mSource.calculateBoundingRects(new Rect(0, 0, 500, 100), false);
+
+        assertEquals(1, rects.length);
+        assertEquals(new Rect(100, 0, 400, 100), rects[0]);
+    }
+
+    @Test
+    public void testCalculateBoundingRects_boundingRectOutsideFrameInWindow_dropped() {
+        mSource.setFrame(new Rect(0, 0, 1000, 100));
+        mSource.setBoundingRects(new Rect[]{
+                new Rect(700, 0, 1000, 100), // Inside |frame|, but outside |relativeFrame|.
+        });
+
+        final Rect[] rects = mSource.calculateBoundingRects(new Rect(0, 0, 500, 100), false);
+
+        assertEquals(0, rects.length);
+    }
+
+    @Test
+    public void testCalculateBoundingRects_boundingRectPartlyOutsideFrameInWindow_cropped() {
+        mSource.setFrame(new Rect(0, 0, 1000, 100));
+        mSource.setBoundingRects(new Rect[]{
+                new Rect(400, 0, 600, 100), // Inside |frame|, and only half inside |relativeFrame|.
+        });
+
+        final Rect[] rects = mSource.calculateBoundingRects(new Rect(0, 0, 500, 100), false);
+
+        assertEquals(1, rects.length);
+        assertEquals(new Rect(400, 0, 500, 100), rects[0]);
+    }
+
+    @Test
+    public void testCalculateBoundingRects_framesNotAtOrigin_resultRelativeToWindowFrame() {
+        mSource.setFrame(new Rect(100, 100, 1100, 200));
+        mSource.setBoundingRects(new Rect[]{
+                new Rect(0, 0, 300, 100), // 300x100 aligned to left.
+                new Rect(800, 0, 1000, 100) // 200x100 align to right.
+        });
+
+        final Rect[] rects = mSource.calculateBoundingRects(new Rect(100, 100, 1100, 1100), false);
+
+        assertEquals(2, rects.length);
+        assertEquals(new Rect(0, 0, 300, 100), rects[0]);
+        assertEquals(new Rect(800, 0, 1000, 100), rects[1]);
+    }
+
+    @Test
+    public void testCalculateBoundingRects_captionBar() {
+        mCaptionSource.setFrame(new Rect(0, 0, 1000, 100));
+        mCaptionSource.setBoundingRects(new Rect[]{
+                new Rect(0, 0, 200, 100), // 200x100, aligned left.
+                new Rect(800, 0, 1000, 100) // 200x100, aligned right.
+        });
+
+        final Rect[] rects = mCaptionSource.calculateBoundingRects(
+                new Rect(0, 0, 1000, 1000), false);
+
+        assertEquals(2, rects.length);
+        assertEquals(new Rect(0, 0, 200, 100), rects[0]);
+        assertEquals(new Rect(800, 0, 1000, 100), rects[1]);
+    }
+
+    @Test
+    public void testCalculateBoundingRects_captionBarFrameMisaligned_rectsFixedToTop() {
+        mCaptionSource.setFrame(new Rect(500, 500, 1500, 600));
+        mCaptionSource.setBoundingRects(new Rect[]{
+                new Rect(0, 0, 100, 100), // 100x100, aligned to left/top of frame
+        });
+
+        final Rect[] rects = mCaptionSource.calculateBoundingRects(
+                new Rect(495, 495, 1500, 1500), false);
+
+        assertEquals(1, rects.length);
+        // rect should be aligned to the top of relative frame, as if the caption frame had been
+        // corrected to be aligned at the top.
+        assertEquals(new Rect(0, 0, 100, 100), rects[0]);
+    }
+
+    @Test
+    public void testCalculateBoundingRects_imeCaptionBarFrameMisaligned_rectsFixedToBottom() {
+        mImeCaptionSource.setFrame(new Rect(500, 1400, 1500, 1500));
+        mImeCaptionSource.setBoundingRects(new Rect[]{
+                new Rect(0, 0, 100, 100), // 100x100, aligned to left/top of frame
+        });
+
+        final Rect[] rects = mImeCaptionSource.calculateBoundingRects(
+                new Rect(495, 495, 1500, 1500), false);
+
+        assertEquals(1, rects.length);
+        // rect should be aligned to the bottom of relative frame, as if the ime caption frame had
+        // been corrected to be aligned at the top.
+        assertEquals(new Rect(0, 905, 100, 1005), rects[0]);
+    }
+
+    @Test
+    public void testCalculateBoundingRects_imeCaptionBar() {
+        mImeCaptionSource.setFrame(new Rect(0, 900, 1000, 1000)); // Frame at the bottom.
+        mImeCaptionSource.setBoundingRects(new Rect[]{
+                new Rect(0, 0, 200, 100), // 200x100, aligned left.
+        });
+
+        final Rect[] rects = mImeCaptionSource.calculateBoundingRects(
+                new Rect(0, 0, 1000, 1000), false);
+
+        assertEquals(1, rects.length);
+        assertEquals(new Rect(0, 900, 200, 1000), rects[0]);
+    }
+
+    @Test
+    public void testCalculateBoundingRects_invisible() {
+        mSource.setFrame(new Rect(0, 0, 1000, 100));
+        mSource.setBoundingRects(new Rect[]{
+                new Rect(0, 0, 300, 100),
+                new Rect(800, 0, 1000, 100),
+        });
+        mSource.setVisible(false);
+
+        final Rect[] rects = mSource.calculateBoundingRects(new Rect(0, 0, 1000, 1000),
+                false /* ignoreVisibility */);
+
+        assertEquals(0, rects.length);
+    }
+
+    @Test
+    public void testCalculateBoundingRects_ignoreVisibility() {
+        mSource.setFrame(new Rect(0, 0, 1000, 100));
+        mSource.setBoundingRects(new Rect[]{
+                new Rect(0, 0, 300, 100),
+                new Rect(800, 0, 1000, 100),
+        });
+        mSource.setVisible(false);
+
+        final Rect[] rects = mSource.calculateBoundingRects(new Rect(0, 0, 1000, 1000),
+                true /* ignoreVisibility */);
+
+        assertEquals(2, rects.length);
+        assertEquals(new Rect(0, 0, 300, 100), rects[0]);
+        assertEquals(new Rect(800, 0, 1000, 100), rects[1]);
+    }
+
     // Parcel and equals already tested via InsetsStateTest
 }
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 672875a..16bd20a 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -63,6 +63,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
+
 /**
  * Tests for {@link InsetsState}.
  *
@@ -88,6 +90,8 @@
             null /* owner */, 1 /* index */, navigationBars());
     private static final int ID_BOTTOM_GESTURES = InsetsSource.createId(
             null /* owner */, 0 /* index */, systemGestures());
+    private static final int ID_EXTRA_CAPTION_BAR = InsetsSource.createId(
+            null /* owner */, 2 /* index */, captionBar());
 
     private final InsetsState mState = new InsetsState();
     private final InsetsState mState2 = new InsetsState();
@@ -420,9 +424,11 @@
     public void testEquals_visibility() {
         mState.getOrCreateSource(ID_IME, ime())
                 .setFrame(new Rect(0, 0, 100, 100))
+                .setBoundingRects(new Rect[]{ new Rect(0, 0, 10, 10) })
                 .setVisible(true);
         mState2.getOrCreateSource(ID_IME, ime())
-                .setFrame(new Rect(0, 0, 100, 100));
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setBoundingRects(new Rect[]{ new Rect(0, 0, 10, 10) });
         assertNotEqualsAndHashCode();
     }
 
@@ -441,6 +447,30 @@
     }
 
     @Test
+    public void testEquals_sameBoundingRects() {
+        mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setBoundingRects(new Rect[]{ new Rect(0, 0, 10, 10) })
+                .setVisible(true);
+        mState2.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setBoundingRects(new Rect[]{ new Rect(0, 0, 10, 10) });
+        assertEqualsAndHashCode();
+    }
+
+    @Test
+    public void testEquals_differentBoundingRects() {
+        mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setBoundingRects(new Rect[]{ new Rect(0, 0, 10, 10) })
+                .setVisible(true);
+        mState2.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setBoundingRects(new Rect[]{ new Rect(0, 0, 20, 20) });
+        assertNotEqualsAndHashCode();
+    }
+
+    @Test
     public void testEquals_samePrivacyIndicator() {
         Rect one = new Rect(0, 1, 2, 3);
         Rect two = new Rect(4, 5, 6, 7);
@@ -734,4 +764,94 @@
         assertEquals(1, onIdNotFoundInState2Called[0]); // 1000.
         assertEquals(1, onFinishCalled[0]);
     }
+
+    @Test
+    public void testCalculateBoundingRects() {
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 1000, 100))
+                .setBoundingRects(null)
+                .setVisible(true);
+        mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+                .setFrame(new Rect(0, 0, 1000, 100))
+                .setBoundingRects(new Rect[]{
+                        new Rect(0, 0, 200, 100),
+                        new Rect(800, 0, 1000, 100)
+                })
+                .setVisible(true);
+        SparseIntArray typeSideMap = new SparseIntArray();
+
+        WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 1000, 1000), null, false,
+                SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
+                typeSideMap);
+
+        assertEquals(
+                List.of(new Rect(0, 0, 1000, 100)),
+                insets.getBoundingRects(Type.statusBars())
+        );
+        assertEquals(
+                List.of(
+                        new Rect(0, 0, 200, 100),
+                        new Rect(800, 0, 1000, 100)
+                ),
+                insets.getBoundingRects(Type.captionBar())
+        );
+    }
+
+    @Test
+    public void testCalculateBoundingRects_multipleSourcesOfSameType_concatenated() {
+        mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+                .setFrame(new Rect(0, 0, 1000, 100))
+                .setBoundingRects(new Rect[]{new Rect(0, 0, 200, 100)})
+                .setVisible(true);
+        mState.getOrCreateSource(ID_EXTRA_CAPTION_BAR, captionBar())
+                .setFrame(new Rect(0, 0, 1000, 100))
+                .setBoundingRects(new Rect[]{new Rect(800, 0, 1000, 100)})
+                .setVisible(true);
+        SparseIntArray typeSideMap = new SparseIntArray();
+
+        WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 1000, 1000), null, false,
+                SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
+                typeSideMap);
+
+        final List<Rect> expected = List.of(
+                new Rect(0, 0, 200, 100),
+                new Rect(800, 0, 1000, 100)
+        );
+        final List<Rect> actual = insets.getBoundingRects(captionBar());
+        assertEquals(expected.size(), actual.size());
+
+        // Order does not matter.
+        assertTrue(actual.containsAll(expected));
+    }
+
+    @Test
+    public void testCalculateBoundingRects_captionBar_reportedAsSysGesturesAndTappableElement() {
+        mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+                .setFrame(new Rect(0, 0, 1000, 100))
+                .setBoundingRects(new Rect[]{new Rect(0, 0, 200, 100)})
+                .setVisible(true);
+        SparseIntArray typeSideMap = new SparseIntArray();
+
+        WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 1000, 1000), null, false,
+                SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, ACTIVITY_TYPE_UNDEFINED,
+                typeSideMap);
+
+        assertEquals(
+                List.of(new Rect(0, 0, 200, 100)),
+                insets.getBoundingRects(Type.captionBar())
+        );
+        assertEquals(
+                List.of(new Rect(0, 0, 200, 100)),
+                insets.getBoundingRects(Type.systemGestures())
+        );
+        assertEquals(
+                List.of(new Rect(0, 0, 200, 100)),
+                insets.getBoundingRects(Type.mandatorySystemGestures())
+        );
+        assertEquals(
+                List.of(new Rect(0, 0, 200, 100)),
+                insets.getBoundingRects(Type.tappableElement())
+        );
+
+    }
 }
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index 69abf5f..ab4543c 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -41,14 +41,14 @@
     public void systemWindowInsets_afterConsuming_isConsumed() {
         assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null,
                 null, false, 0, 0, null, null, null, null,
-                WindowInsets.Type.systemBars(), false)
+                WindowInsets.Type.systemBars(), false, null, null, 0, 0)
                 .consumeSystemWindowInsets().isConsumed());
     }
 
     @Test
     public void multiNullConstructor_isConsumed() {
         assertTrue(new WindowInsets(null, null, null, false, 0, 0, null, null, null, null,
-                WindowInsets.Type.systemBars(), false).isConsumed());
+                WindowInsets.Type.systemBars(), false, null, null, 0, 0).isConsumed());
     }
 
     @Test
@@ -65,7 +65,7 @@
         WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0));
         WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, 0,
                 0, null, null, null, DisplayShape.NONE, systemBars(),
-                true /* compatIgnoreVisibility */);
+                true /* compatIgnoreVisibility */, null, null, 0, 0);
         assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
     }
 }
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index f39bddd..51eb41c 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -20,10 +20,12 @@
 import static android.view.MotionEvent.ACTION_HOVER_MOVE;
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
+import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
 import static android.view.stylus.HandwritingTestUtil.createView;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -91,6 +93,7 @@
     private EditText mTestView1;
     private EditText mTestView2;
     private Context mContext;
+    private boolean mInitiateWithoutConnection;
 
     @Before
     public void setup() throws Exception {
@@ -119,6 +122,7 @@
         mHandwritingInitiator.updateHandwritingAreasForView(mTestView1);
         mHandwritingInitiator.updateHandwritingAreasForView(mTestView2);
         doReturn(true).when(mHandwritingInitiator).tryAcceptStylusHandwritingDelegation(any());
+        mInitiateWithoutConnection = initiationWithoutInputConnection();
     }
 
     @Test
@@ -194,7 +198,9 @@
         mTestView1.setText("hello");
         when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
 
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        if (!mInitiateWithoutConnection) {
+            mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        }
         final int x1 = sHwArea1.left - HW_BOUNDS_OFFSETS_LEFT_PX / 2;
         final int y1 = sHwArea1.top - HW_BOUNDS_OFFSETS_TOP_PX / 2;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
@@ -214,7 +220,7 @@
     }
 
     @Test
-    public void onTouchEvent_startHandwriting_inputConnectionBuiltAfterStylusMove() {
+    public void onTouchEvent_startHandwriting_servedViewUpdateAfterStylusMove() {
         final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
         final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
@@ -225,14 +231,19 @@
         MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
-        // InputConnection is created after stylus movement.
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        if (mInitiateWithoutConnection) {
+            // Focus is changed after stylus movement.
+            mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true);
+        } else {
+            // InputConnection is created after stylus movement.
+            mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        }
 
         verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
     }
 
     @Test
-    public void onTouchEvent_startHandwriting_inputConnectionBuilt_stylusMoveInExtendedHWArea() {
+    public void onTouchEvent_startHandwriting_servedViewUpdate_stylusMoveInExtendedHWArea() {
         mTestView1.setText("hello");
         when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
         // The stylus down point is between mTestView1 and  mTestView2, but it is within the
@@ -247,21 +258,35 @@
         MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
-        // First create InputConnection for mTestView2 and verify that handwriting is not started.
-        mHandwritingInitiator.onInputConnectionCreated(mTestView2);
-        verify(mHandwritingInitiator, never()).startHandwriting(mTestView2);
+        if (!mInitiateWithoutConnection) {
+            // First create InputConnection for mTestView2 and verify that handwriting is not
+            // started.
+            mHandwritingInitiator.onInputConnectionCreated(mTestView2);
+        }
 
-        // Next create InputConnection for mTextView1. Handwriting is started for this view since
-        // the stylus down point is closest to this view.
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        // Note: mTestView2 receives focus when initiationWithoutInputConnection() is enabled.
+        //  verify that handwriting is not started.
+        verify(mHandwritingInitiator, never()).startHandwriting(mTestView2);
+        if (mInitiateWithoutConnection) {
+            // Focus is changed after stylus movement.
+            mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true);
+        } else {
+            // Next create InputConnection for mTextView1. Handwriting is started for this view
+            // since the stylus down point is closest to this view.
+            mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        }
+        // Handwriting is started for this view since  the stylus down point is closest to this
+        // view.
         verify(mHandwritingInitiator).startHandwriting(mTestView1);
         // Since the stylus down point was outside the TextView's bounds, the handwriting initiator
         // sets the cursor position.
         verify(mTestView1).setSelection(4);
     }
 
+
     @Test
     public void onTouchEvent_tryAcceptDelegation_delegatorCallbackCreatesInputConnection() {
+        assumeFalse(mInitiateWithoutConnection);
         View delegateView = new EditText(mContext);
         delegateView.setIsHandwritingDelegate(true);
 
@@ -281,6 +306,7 @@
         verify(mHandwritingInitiator, times(1)).tryAcceptStylusHandwritingDelegation(delegateView);
     }
 
+
     @Test
     public void onTouchEvent_tryAcceptDelegation_delegatorCallbackFocusesDelegate() {
         View delegateView = new EditText(mContext);
@@ -288,8 +314,14 @@
         mHandwritingInitiator.onInputConnectionCreated(delegateView);
         reset(mHandwritingInitiator);
 
-        mTestView1.setHandwritingDelegatorCallback(
-                () -> mHandwritingInitiator.onDelegateViewFocused(delegateView));
+        if (mInitiateWithoutConnection) {
+            mTestView1.setHandwritingDelegatorCallback(
+                    () -> mHandwritingInitiator.updateFocusedView(
+                            delegateView, /*fromTouchEvent*/ false));
+        } else  {
+            mTestView1.setHandwritingDelegatorCallback(
+                    () -> mHandwritingInitiator.onDelegateViewFocused(delegateView));
+        }
 
         final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
         final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
@@ -339,6 +371,14 @@
         assertThat(onTouchEventResult4).isTrue();
     }
 
+    private void callOnInputConnectionOrUpdateViewFocus(View view) {
+        if (mInitiateWithoutConnection) {
+            mHandwritingInitiator.updateFocusedView(view, /*fromTouchEvent*/ true);
+        } else {
+            mHandwritingInitiator.onInputConnectionCreated(view);
+        }
+    }
+
     @Test
     public void onTouchEvent_notStartHandwriting_whenHandwritingNotAvailable() {
         final Rect rect = new Rect(600, 600, 900, 900);
@@ -346,7 +386,7 @@
                 false /* isStylusHandwritingAvailable */);
         mHandwritingInitiator.updateHandwritingAreasForView(testView);
 
-        mHandwritingInitiator.onInputConnectionCreated(testView);
+        callOnInputConnectionOrUpdateViewFocus(testView);
         final int x1 = (rect.left + rect.right) / 2;
         final int y1 = (rect.top + rect.bottom) / 2;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
@@ -365,7 +405,7 @@
 
     @Test
     public void onTouchEvent_notStartHandwriting_when_stylusTap_withinHWArea() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        callOnInputConnectionOrUpdateViewFocus(mTestView1);
         final int x1 = 200;
         final int y1 = 200;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
@@ -381,7 +421,7 @@
 
     @Test
     public void onTouchEvent_notStartHandwriting_when_stylusMove_outOfHWArea() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        callOnInputConnectionOrUpdateViewFocus(mTestView1);
         final int x1 = 10;
         final int y1 = 10;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
@@ -397,7 +437,7 @@
 
     @Test
     public void onTouchEvent_notStartHandwriting_when_stylusMove_afterTimeOut() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        callOnInputConnectionOrUpdateViewFocus(mTestView1);
         final int x1 = 10;
         final int y1 = 10;
         final long time1 = 10L;
@@ -433,8 +473,9 @@
 
     @Test
     public void onTouchEvent_focusView_inputConnectionAlreadyBuilt_stylusMoveOnce_withinHWArea() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
-
+        if (!mInitiateWithoutConnection) {
+            mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        }
         final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
         final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
@@ -487,14 +528,14 @@
 
         verify(mTestView2, times(1)).requestFocus();
 
-        mHandwritingInitiator.onInputConnectionCreated(mTestView2);
+        callOnInputConnectionOrUpdateViewFocus(mTestView2);
         verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView2);
     }
 
     @Test
     public void onTouchEvent_handwritingAreaOverlapped_focusedViewHasPriority() {
         // Simulate the case where mTestView1 is focused.
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        callOnInputConnectionOrUpdateViewFocus(mTestView1);
         // The ACTION_DOWN location is within the handwriting bounds of both mTestView1 and
         // mTestView2. Although it's closer to mTestView2's handwriting bounds, handwriting is
         // initiated for mTestView1 because it's focused.
@@ -559,9 +600,14 @@
         // Set mTextView2 to be the delegate of mTestView1.
         mTestView2.setIsHandwritingDelegate(true);
 
-        mTestView1.setHandwritingDelegatorCallback(
-                () -> mHandwritingInitiator.onInputConnectionCreated(mTestView2));
-
+        if (mInitiateWithoutConnection) {
+            mTestView1.setHandwritingDelegatorCallback(
+                    () -> mHandwritingInitiator.updateFocusedView(
+                            mTestView2, /*fromTouchEvent*/ false));
+        } else {
+            mTestView1.setHandwritingDelegatorCallback(
+                    () -> mHandwritingInitiator.onInputConnectionCreated(mTestView2));
+        }
         injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(),
                 /* exceedsHWSlop */ true);
         // Prerequisite check, verify that handwriting started for delegateView.
@@ -610,8 +656,13 @@
         assertThat(icon1).isNull();
 
         // Simulate that focus is switched to mTestView2 first and then switched back.
-        mHandwritingInitiator.onInputConnectionCreated(mTestView2);
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        if (mInitiateWithoutConnection) {
+            mHandwritingInitiator.updateFocusedView(mTestView2, /*fromTouchEvent*/ true);
+            mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true);
+        } else {
+            mHandwritingInitiator.onInputConnectionCreated(mTestView2);
+            mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        }
 
         PointerIcon icon2 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent1);
         // After the change of focus, hover icon shows again.
@@ -620,9 +671,15 @@
 
     @Test
     public void autoHandwriting_whenDisabled_wontStartHW() {
-        View mockView = createView(sHwArea1, false /* autoHandwritingEnabled */,
-                true /* isStylusHandwritingAvailable */);
-        mHandwritingInitiator.onInputConnectionCreated(mockView);
+        if (mInitiateWithoutConnection) {
+            mTestView1.setAutoHandwritingEnabled(false);
+            mTestView1.setHandwritingDelegatorCallback(null);
+            mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true);
+        } else {
+            View mockView = createView(sHwArea1, false /* autoHandwritingEnabled */,
+                    true /* isStylusHandwritingAvailable */);
+            mHandwritingInitiator.onInputConnectionCreated(mockView);
+        }
         final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
         final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
@@ -639,6 +696,7 @@
 
     @Test
     public void onInputConnectionCreated() {
+        assumeFalse(mInitiateWithoutConnection);
         mHandwritingInitiator.onInputConnectionCreated(mTestView1);
         assertThat(mHandwritingInitiator.mConnectedView).isNotNull();
         assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView1);
@@ -646,6 +704,7 @@
 
     @Test
     public void onInputConnectionCreated_whenAutoHandwritingIsDisabled() {
+        assumeFalse(mInitiateWithoutConnection);
         View view = new View(mContext);
         view.setAutoHandwritingEnabled(false);
         assertThat(view.isAutoHandwritingEnabled()).isFalse();
@@ -656,6 +715,7 @@
 
     @Test
     public void onInputConnectionClosed() {
+        assumeFalse(mInitiateWithoutConnection);
         mHandwritingInitiator.onInputConnectionCreated(mTestView1);
         mHandwritingInitiator.onInputConnectionClosed(mTestView1);
 
@@ -664,6 +724,7 @@
 
     @Test
     public void onInputConnectionClosed_whenAutoHandwritingIsDisabled() {
+        assumeFalse(mInitiateWithoutConnection);
         View view = new View(mContext);
         view.setAutoHandwritingEnabled(false);
         mHandwritingInitiator.onInputConnectionCreated(view);
@@ -674,6 +735,7 @@
 
     @Test
     public void onInputConnectionCreated_inputConnectionRestarted() {
+        assumeFalse(mInitiateWithoutConnection);
         // When IMM restarts input connection, View#onInputConnectionCreatedInternal might be
         // called before View#onInputConnectionClosedInternal. As a result, we need to handle the
         // case where "one view "2 InputConnections".
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
index 84dd274..8d66cfc 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
@@ -169,7 +169,8 @@
 
     private WindowInsets insetsWith(Insets content, DisplayCutout cutout) {
         return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null,
-                false, 0, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false);
+                false, 0, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false,
+                null, null, 0, 0);
     }
 
     private ViewGroup createViewGroupWithId(int id) {
diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java
index 08333ec..bf9221a 100644
--- a/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/NotificationOptimizedLinearLayoutComparisonTest.java
@@ -31,6 +31,7 @@
 import android.view.View.MeasureSpec;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
+import android.widget.TextView;
 import android.widget.flags.Flags;
 
 import androidx.test.InstrumentationRegistry;
@@ -73,7 +74,7 @@
 
     private static final int[] LAYOUT_PARAMS = {MATCH_PARENT, WRAP_CONTENT, 0, 50};
     private static final int[] CHILD_WEIGHTS = {0, 1};
-
+    private static final int[] CHILD_MARGINS = {0, 10, -10};
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
@@ -84,35 +85,96 @@
         mContext = InstrumentationRegistry.getTargetContext();
     }
 
+
     @Test
     public void test() throws Throwable {
+        final List<View> controlChildren =
+                new ArrayList<>();
+        final List<View> testChildren =
+                new ArrayList<>();
+
+        final View controlChild1 = buildChildView();
+        final View controlChild2 = buildChildView();
+        controlChildren.add(controlChild1);
+        controlChildren.add(controlChild2);
+
+        final View testChild1 = buildChildView();
+        final View testChild2 = buildChildView();
+        testChildren.add(testChild1);
+        testChildren.add(testChild2);
+
+        final LinearLayout controlContainer = buildLayout(false, controlChildren);
+
+        final LinearLayout testContainer = buildLayout(true, testChildren);
+
+        final LinearLayout.LayoutParams firstChildLayoutParams = new LinearLayout.LayoutParams(0,
+                0);
+        final LinearLayout.LayoutParams secondChildLayoutParams = new LinearLayout.LayoutParams(0,
+                0);
+        controlChild1.setLayoutParams(firstChildLayoutParams);
+        controlChild2.setLayoutParams(secondChildLayoutParams);
+        testChild1.setLayoutParams(firstChildLayoutParams);
+        testChild2.setLayoutParams(secondChildLayoutParams);
+
         for (int orientation : ORIENTATIONS) {
-            for (int widthSpec : MEASURE_SPECS) {
-                for (int heightSpec : MEASURE_SPECS) {
-                    for (int firstChildGravity : GRAVITIES) {
-                        for (int secondChildGravity : GRAVITIES) {
-                            for (int firstChildLayoutWidth : LAYOUT_PARAMS) {
-                                for (int firstChildLayoutHeight : LAYOUT_PARAMS) {
-                                    for (int secondChildLayoutWidth : LAYOUT_PARAMS) {
-                                        for (int secondChildLayoutHeight : LAYOUT_PARAMS) {
+            controlContainer.setOrientation(orientation);
+            testContainer.setOrientation(orientation);
+
+            for (int firstChildLayoutWidth : LAYOUT_PARAMS) {
+                firstChildLayoutParams.width = firstChildLayoutWidth;
+                for (int firstChildLayoutHeight : LAYOUT_PARAMS) {
+                    firstChildLayoutParams.height = firstChildLayoutHeight;
+
+                    for (int secondChildLayoutWidth : LAYOUT_PARAMS) {
+                        secondChildLayoutParams.width = secondChildLayoutWidth;
+                        for (int secondChildLayoutHeight : LAYOUT_PARAMS) {
+                            secondChildLayoutParams.height = secondChildLayoutHeight;
+
+                            for (int firstChildMargin : CHILD_MARGINS) {
+                                firstChildLayoutParams.setMargins(firstChildMargin,
+                                        firstChildMargin, firstChildMargin, firstChildMargin);
+                                for (int secondChildMargin : CHILD_MARGINS) {
+                                    secondChildLayoutParams.setMargins(secondChildMargin,
+                                            secondChildMargin, secondChildMargin,
+                                            secondChildMargin);
+
+                                    for (int firstChildGravity : GRAVITIES) {
+                                        firstChildLayoutParams.gravity = firstChildGravity;
+                                        for (int secondChildGravity : GRAVITIES) {
+                                            secondChildLayoutParams.gravity = secondChildGravity;
+
                                             for (int firstChildWeight : CHILD_WEIGHTS) {
+                                                firstChildLayoutParams.weight = firstChildWeight;
                                                 for (int secondChildWeight : CHILD_WEIGHTS) {
-                                                    executeTest(/*testSpec =*/createTestSpec(
-                                                            orientation,
-                                                            widthSpec, heightSpec,
-                                                            firstChildLayoutWidth,
-                                                            firstChildLayoutHeight,
-                                                            secondChildLayoutWidth,
-                                                            secondChildLayoutHeight,
-                                                            firstChildGravity,
-                                                            secondChildGravity,
-                                                            firstChildWeight,
-                                                            secondChildWeight));
+                                                    secondChildLayoutParams.weight =
+                                                            secondChildWeight;
+
+                                                    for (int widthSpec : MEASURE_SPECS) {
+                                                        for (int heightSpec : MEASURE_SPECS) {
+                                                            executeTest(controlContainer,
+                                                                    testContainer,
+                                                                    createTestSpec(
+                                                                            orientation,
+                                                                            widthSpec, heightSpec,
+                                                                            firstChildLayoutWidth,
+                                                                            firstChildLayoutHeight,
+                                                                            secondChildLayoutWidth,
+                                                                            secondChildLayoutHeight,
+                                                                            firstChildGravity,
+                                                                            secondChildGravity,
+                                                                            firstChildWeight,
+                                                                            secondChildWeight,
+                                                                            firstChildMargin,
+                                                                            secondChildMargin)
+                                                            );
+                                                        }
+                                                    }
                                                 }
                                             }
                                         }
                                     }
                                 }
+
                             }
                         }
                     }
@@ -121,47 +183,8 @@
         }
     }
 
-    private void executeTest(TestSpec testSpec) {
-        // GIVEN
-        final List<View> controlChildren =
-                new ArrayList<>();
-        final List<View> testChildren =
-                new ArrayList<>();
-
-        controlChildren.add(
-                buildChildView(
-                        testSpec.mFirstChildLayoutWidth,
-                        testSpec.mFirstChildLayoutHeight,
-                        testSpec.mFirstChildGravity,
-                        testSpec.mFirstChildWeight));
-        controlChildren.add(
-                buildChildView(
-                        testSpec.mSecondChildLayoutWidth,
-                        testSpec.mSecondChildLayoutHeight,
-                        testSpec.mSecondChildGravity,
-                        testSpec.mSecondChildWeight));
-
-        testChildren.add(
-                buildChildView(
-                        testSpec.mFirstChildLayoutWidth,
-                        testSpec.mFirstChildLayoutHeight,
-                        testSpec.mFirstChildGravity,
-                        testSpec.mFirstChildWeight));
-        testChildren.add(
-                buildChildView(
-                        testSpec.mSecondChildLayoutWidth,
-                        testSpec.mSecondChildLayoutHeight,
-                        testSpec.mSecondChildGravity,
-                        testSpec.mSecondChildWeight));
-
-        final LinearLayout controlContainer = buildLayout(false,
-                testSpec.mOrientation,
-                controlChildren);
-
-        final LinearLayout testContainer = buildLayout(true,
-                testSpec.mOrientation,
-                testChildren);
-
+    private void executeTest(LinearLayout controlContainer, LinearLayout testContainer,
+            TestSpec testSpec) {
         // WHEN
         controlContainer.measure(testSpec.mWidthSpec, testSpec.mHeightSpec);
         testContainer.measure(testSpec.mWidthSpec, testSpec.mHeightSpec);
@@ -171,6 +194,7 @@
         assertLayoutsEqual("Test Case:" + testSpec, controlContainer, testContainer);
     }
 
+
     private static class TestSpec {
         private final int mOrientation;
         private final int mWidthSpec;
@@ -183,6 +207,8 @@
         private final int mSecondChildGravity;
         private final int mFirstChildWeight;
         private final int mSecondChildWeight;
+        private final int mFirstChildMargin;
+        private final int mSecondChildMargin;
 
         TestSpec(
                 int orientation,
@@ -195,7 +221,9 @@
                 int firstChildGravity,
                 int secondChildGravity,
                 int firstChildWeight,
-                int secondChildWeight) {
+                int secondChildWeight,
+                int firstChildMargin,
+                int secondChildMargin) {
             mOrientation = orientation;
             mWidthSpec = widthSpec;
             mHeightSpec = heightSpec;
@@ -207,6 +235,8 @@
             mSecondChildGravity = secondChildGravity;
             mFirstChildWeight = firstChildWeight;
             mSecondChildWeight = secondChildWeight;
+            mFirstChildMargin = firstChildMargin;
+            mSecondChildMargin = secondChildMargin;
         }
 
         @Override
@@ -223,6 +253,8 @@
                     + ", mSecondChildGravity=" + mSecondChildGravity
                     + ", mFirstChildWeight=" + mFirstChildWeight
                     + ", mSecondChildWeight=" + mSecondChildWeight
+                    + ", mFirstChildMargin=" + mFirstChildMargin
+                    + ", mSecondChildMargin=" + mSecondChildMargin
                     + '}';
         }
 
@@ -246,15 +278,13 @@
         }
     }
 
-    private LinearLayout buildLayout(boolean isNotificationOptimized,
-            @LinearLayout.OrientationMode int orientation, List<View> children) {
+    private LinearLayout buildLayout(boolean isNotificationOptimized, List<View> children) {
         final LinearLayout linearLayout;
         if (isNotificationOptimized) {
             linearLayout = new NotificationOptimizedLinearLayout(mContext);
         } else {
             linearLayout = new LinearLayout(mContext);
         }
-        linearLayout.setOrientation(orientation);
         for (int i = 0; i < children.size(); i++) {
             linearLayout.addView(children.get(i));
         }
@@ -262,7 +292,8 @@
     }
 
     private void assertLayoutsEqual(String testCase, View controlView, View testView) {
-        mExpect.withMessage("MeasuredWidths are not equal. Test Case:" + testCase)
+        mExpect.withMessage(
+                        "MeasuredWidths are not equal. Test Case:" + testCase)
                 .that(testView.getMeasuredWidth()).isEqualTo(controlView.getMeasuredWidth());
         mExpect.withMessage("MeasuredHeights are not equal. Test Case:" + testCase)
                 .that(testView.getMeasuredHeight()).isEqualTo(controlView.getMeasuredHeight());
@@ -286,23 +317,12 @@
         }
     }
 
-    private static class TestView extends View {
-        TestView(Context context) {
-            super(context);
-        }
-
-        @Override
-        public int getBaseline() {
-            return 5;
-        }
-    }
-
-
     private TestSpec createTestSpec(int orientation,
             int widthSpec, int heightSpec,
             int firstChildLayoutWidth, int firstChildLayoutHeight, int secondChildLayoutWidth,
             int secondChildLayoutHeight, int firstChildGravity, int secondChildGravity,
-            int firstChildWeight, int secondChildWeight) {
+            int firstChildWeight, int secondChildWeight, int firstChildMargin,
+            int secondChildMargin) {
 
         return new TestSpec(
                 orientation,
@@ -314,16 +334,16 @@
                 firstChildGravity,
                 secondChildGravity,
                 firstChildWeight,
-                secondChildWeight);
+                secondChildWeight,
+                firstChildMargin,
+                secondChildMargin);
     }
 
-    private View buildChildView(int childLayoutWidth, int childLayoutHeight,
-            int childGravity, int childWeight) {
-        final View childView = new TestView(mContext);
-        // Set desired size using LayoutParams
-        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(childLayoutWidth,
-                childLayoutHeight, childWeight);
-        params.gravity = childGravity;
+    private View buildChildView() {
+        final View childView = new TextView(mContext);
+        // this is initial value. We are going to mutate this layout params during the test.
+        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(MATCH_PARENT,
+                WRAP_CONTENT);
         childView.setLayoutParams(params);
         return childView;
     }
diff --git a/core/tests/devicestatetests/Android.bp b/core/tests/devicestatetests/Android.bp
index 7748de5..60848b3 100644
--- a/core/tests/devicestatetests/Android.bp
+++ b/core/tests/devicestatetests/Android.bp
@@ -29,6 +29,8 @@
         "androidx.test.rules",
         "frameworks-base-testutils",
         "mockito-target-minus-junit4",
+        "platform-test-annotations",
+        "testng",
     ],
     libs: ["android.test.runner"],
     platform_apis: true,
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
similarity index 93%
rename from services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
rename to core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
index d54524e..396d403 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.devicestate;
+package android.hardware.devicestate;
 
 import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
@@ -25,18 +25,17 @@
 
 import android.platform.test.annotations.Presubmit;
 
-import androidx.test.runner.AndroidJUnit4;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
 /**
- * Unit tests for {@link DeviceState}.
+ * Unit tests for {@link android.hardware.devicestate.DeviceState}.
  * <p/>
  * Run with <code>atest DeviceStateTest</code>.
  */
 @Presubmit
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnit4.class)
 public final class DeviceStateTest {
     @Test
     public void testConstruct() {
diff --git a/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java b/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java
index 7723d58..f91172d 100644
--- a/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java
@@ -45,7 +45,6 @@
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-@IgnoreUnderRavenwood(blockedBy = Xml.class)
 public class FastXmlSerializerTest {
     private static final String TAG = "FastXmlSerializerTest";
 
@@ -146,6 +145,7 @@
 
     @Test
     @LargeTest
+    @IgnoreUnderRavenwood(reason = "Long test runtime")
     public void testAllCharacters() throws Exception {
         boolean ok = true;
         for (int i = 0; i < 0xffff; i++) {
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index d8713f7a..fdb5208 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -575,6 +575,9 @@
         <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/>
         <!-- Permissions required for CTS test - CtsContactKeysProviderPrivilegedApp -->
         <permission name="android.permission.WRITE_VERIFICATION_STATE_E2EE_CONTACT_KEYS"/>
+        <!-- Permission required for CTS test BlockedNumberContractTest -->
+        <permission name="android.permission.WRITE_BLOCKED_NUMBERS" />
+        <permission name="android.permission.READ_BLOCKED_NUMBERS" />
         <!-- Permission required for CTS test - PackageManagerTest -->
         <permission name="android.permission.DOMAIN_VERIFICATION_AGENT"/>
     </privapp-permissions>
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index e6de597..9159a00 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -96,7 +96,7 @@
      *
      * The contents of the source will be scaled to fit exactly inside the bitmap.
      * The pixel format of the source buffer will be converted, as part of the copy,
-     * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+     * to fit the bitmap's {@link Bitmap.Config}. The most recently queued buffer
      * in the SurfaceView's Surface will be used as the source of the copy.
      *
      * @param source The source from which to copy
@@ -117,7 +117,7 @@
      *
      * The contents of the source will be scaled to fit exactly inside the bitmap.
      * The pixel format of the source buffer will be converted, as part of the copy,
-     * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+     * to fit the bitmap's {@link Bitmap.Config}. The most recently queued buffer
      * in the SurfaceView's Surface will be used as the source of the copy.
      *
      * @param source The source from which to copy
@@ -143,7 +143,7 @@
      *
      * The contents of the source will be scaled to fit exactly inside the bitmap.
      * The pixel format of the source buffer will be converted, as part of the copy,
-     * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+     * to fit the bitmap's {@link Bitmap.Config}. The most recently queued buffer
      * in the Surface will be used as the source of the copy.
      *
      * @param source The source from which to copy
@@ -164,7 +164,7 @@
      *
      * The contents of the source rect will be scaled to fit exactly inside the bitmap.
      * The pixel format of the source buffer will be converted, as part of the copy,
-     * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+     * to fit the bitmap's {@link Bitmap.Config}. The most recently queued buffer
      * in the Surface will be used as the source of the copy.
      *
      * @param source The source from which to copy
@@ -201,7 +201,7 @@
      *
      * The contents of the source will be scaled to fit exactly inside the bitmap.
      * The pixel format of the source buffer will be converted, as part of the copy,
-     * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+     * to fit the bitmap's {@link Bitmap.Config}. The most recently queued buffer
      * in the Window's Surface will be used as the source of the copy.
      *
      * Note: This is limited to being able to copy from Window's with a non-null
@@ -231,7 +231,7 @@
      *
      * The contents of the source rect will be scaled to fit exactly inside the bitmap.
      * The pixel format of the source buffer will be converted, as part of the copy,
-     * to fit the the bitmap's {@link Bitmap.Config}. The most recently queued buffer
+     * to fit the bitmap's {@link Bitmap.Config}. The most recently queued buffer
      * in the Window's Surface will be used as the source of the copy.
      *
      * Note: This is limited to being able to copy from Window's with a non-null
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
index ea7c6ed..5825bbf 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -165,6 +165,25 @@
     }
 
     @Test
+    fun testGetRestingPosition_afterBoundsChange() {
+        positioner.update(defaultDeviceConfig.copy(isLargeScreen = true,
+                windowBounds = Rect(0, 0, 2000, 1600)))
+
+        // Set the resting position to the right side
+        var allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+        val restingPosition = PointF(allowableStackRegion.right, allowableStackRegion.centerY())
+        positioner.restingPosition = restingPosition
+
+        // Now make the device smaller
+        positioner.update(defaultDeviceConfig.copy(isLargeScreen = false,
+                windowBounds = Rect(0, 0, 1000, 1600)))
+
+        // Check the resting position is on the correct side
+        allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
+        assertThat(positioner.restingPosition.x).isEqualTo(allowableStackRegion.right)
+    }
+
+    @Test
     fun testHasUserModifiedDefaultPosition_false() {
         positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
         assertThat(positioner.hasUserModifiedDefaultPosition()).isFalse()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index c03b6f8..cda29c9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -120,6 +120,13 @@
 
     @VisibleForTesting
     public void updateInternal(int rotation, Insets insets, Rect bounds) {
+        BubbleStackView.RelativeStackPosition prevStackPosition = null;
+        if (mRestingStackPosition != null && mScreenRect != null && !mScreenRect.equals(bounds)) {
+            // Save the resting position as a relative position with the previous bounds, at the
+            // end of the update we'll restore it based on the new bounds.
+            prevStackPosition = new BubbleStackView.RelativeStackPosition(getRestingPosition(),
+                    getAllowableStackPositionRegion(1));
+        }
         mRotation = rotation;
         mInsets = insets;
 
@@ -182,6 +189,12 @@
                 R.dimen.bubbles_flyout_min_width_large_screen);
 
         mMaxBubbles = calculateMaxBubbles();
+
+        if (prevStackPosition != null) {
+            // Get the new resting position based on the updated values
+            mRestingStackPosition = prevStackPosition.getAbsolutePositionInRegion(
+                    getAllowableStackPositionRegion(1));
+        }
     }
 
     /**
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 ead5ad2..bd9d89c 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
@@ -64,6 +64,7 @@
 import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
 import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
 import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.draganddrop.UnhandledDragController;
 import com.android.wm.shell.freeform.FreeformComponents;
 import com.android.wm.shell.freeform.FreeformTaskListener;
 import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
@@ -558,6 +559,14 @@
 
     @WMSingleton
     @Provides
+    static UnhandledDragController provideUnhandledDragController(
+            IWindowManager wmService,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new UnhandledDragController(wmService, mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
     static DragAndDropController provideDragAndDropController(Context context,
             ShellInit shellInit,
             ShellController shellController,
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 e872849..42c8d74 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
@@ -70,8 +70,8 @@
 import com.android.wm.shell.transition.OneShotRemoteHandler
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.util.KtProtoLog
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
 import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
+import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
 import java.io.PrintWriter
 import java.util.concurrent.Executor
 import java.util.function.Consumer
@@ -175,6 +175,12 @@
         )
     }
 
+    fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
+        toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
+        enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener)
+        dragToDesktopTransitionHandler.setOnTaskResizeAnimatorListener(listener)
+    }
+
     /** Setter needed to avoid cyclic dependency. */
     fun setSplitScreenController(controller: SplitScreenController) {
         splitScreenController = controller
@@ -236,12 +242,11 @@
 
     /** Move a task with given `taskId` to desktop */
     fun moveToDesktop(
-            decor: DesktopModeWindowDecoration,
             taskId: Int,
             wct: WindowContainerTransaction = WindowContainerTransaction()
     ) {
         shellTaskOrganizer.getRunningTaskInfo(taskId)?.let {
-            task -> moveToDesktop(decor, task, wct)
+            task -> moveToDesktop(task, wct)
         }
     }
 
@@ -283,7 +288,6 @@
      * Move a task to desktop
      */
     fun moveToDesktop(
-            decor: DesktopModeWindowDecoration,
             task: RunningTaskInfo,
             wct: WindowContainerTransaction = WindowContainerTransaction()
     ) {
@@ -298,7 +302,7 @@
         addMoveToDesktopChanges(wct, task)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            enterDesktopTaskTransitionHandler.moveToDesktop(wct, decor)
+            enterDesktopTaskTransitionHandler.moveToDesktop(wct)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
         }
@@ -311,7 +315,6 @@
     fun startDragToDesktop(
             taskInfo: RunningTaskInfo,
             dragToDesktopValueAnimator: MoveToDesktopAnimator,
-            windowDecor: DesktopModeWindowDecoration
     ) {
         KtProtoLog.v(
                 WM_SHELL_DESKTOP_MODE,
@@ -320,8 +323,7 @@
         )
         dragToDesktopTransitionHandler.startDragToDesktopTransition(
                 taskInfo.taskId,
-                dragToDesktopValueAnimator,
-                windowDecor
+                dragToDesktopValueAnimator
         )
     }
 
@@ -522,7 +524,7 @@
     }
 
     /** Quick-resizes a desktop task, toggling between the stable bounds and the default bounds. */
-    fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo, windowDecor: DesktopModeWindowDecoration) {
+    fun toggleDesktopTaskSize(taskInfo: RunningTaskInfo) {
         val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
 
         val stableBounds = Rect()
@@ -543,11 +545,7 @@
 
         val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            toggleResizeDesktopTaskTransitionHandler.startTransition(
-                wct,
-                taskInfo.taskId,
-                windowDecor
-            )
+            toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
         }
@@ -558,11 +556,7 @@
      *
      * @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to.
      */
-    fun snapToHalfScreen(
-            taskInfo: RunningTaskInfo,
-            windowDecor: DesktopModeWindowDecoration,
-            position: SnapPosition
-    ) {
+    fun snapToHalfScreen(taskInfo: RunningTaskInfo, position: SnapPosition) {
         val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
 
         val stableBounds = Rect()
@@ -592,11 +586,7 @@
 
         val wct = WindowContainerTransaction().setBounds(taskInfo.token, destinationBounds)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            toggleResizeDesktopTaskTransitionHandler.startTransition(
-                    wct,
-                    taskInfo.taskId,
-                    windowDecor
-            )
+            toggleResizeDesktopTaskTransitionHandler.startTransition(wct)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 39610e3..af26e29 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -34,9 +34,9 @@
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
 import com.android.wm.shell.transition.Transitions.TransitionHandler
 import com.android.wm.shell.util.KtProtoLog
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
 import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
 import com.android.wm.shell.windowdecor.MoveToDesktopAnimator.Companion.DRAG_FREEFORM_SCALE
+import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
 import java.util.function.Supplier
 
 /**
@@ -69,6 +69,7 @@
     private var dragToDesktopStateListener: DragToDesktopStateListener? = null
     private var splitScreenController: SplitScreenController? = null
     private var transitionState: TransitionState? = null
+    private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
 
     /** Whether a drag-to-desktop transition is in progress. */
     val inProgress: Boolean
@@ -84,6 +85,10 @@
         splitScreenController = controller
     }
 
+    fun setOnTaskResizeAnimatorListener(listener: OnTaskResizeAnimationListener) {
+        onTaskResizeAnimationListener = listener
+    }
+
     /**
      * Starts a transition that performs a transient launch of Home so that Home is brought to the
      * front while still keeping the currently focused task that is being dragged resumed. This
@@ -96,7 +101,6 @@
     fun startDragToDesktopTransition(
             taskId: Int,
             dragToDesktopAnimator: MoveToDesktopAnimator,
-            windowDecoration: DesktopModeWindowDecoration
     ) {
         if (inProgress) {
             KtProtoLog.v(
@@ -128,14 +132,12 @@
             TransitionState.FromSplit(
                     draggedTaskId = taskId,
                     dragAnimator = dragToDesktopAnimator,
-                    windowDecoration = windowDecoration,
                     startTransitionToken = startTransitionToken
             )
         } else {
             TransitionState.FromFullscreen(
                     draggedTaskId = taskId,
                     dragAnimator = dragToDesktopAnimator,
-                    windowDecoration = windowDecoration,
                     startTransitionToken = startTransitionToken
             )
         }
@@ -405,7 +407,7 @@
             // Accept the merge by applying the merging transaction (applied by #showResizeVeil)
             // and finish callback. Show the veil and position the task at the first frame before
             // starting the final animation.
-            state.windowDecoration.showResizeVeil(t, animStartBounds)
+            onTaskResizeAnimationListener.onAnimationStart(state.draggedTaskId, t, animStartBounds)
             finishCallback.onTransitionFinished(null /* wct */)
 
             // Because the task surface was scaled down during the drag, we must use the animated
@@ -429,11 +431,15 @@
                                         animBounds.height()
                                 )
                             }
-                            state.windowDecoration.updateResizeVeil(tx, animBounds)
+                            onTaskResizeAnimationListener.onBoundsChange(
+                                    state.draggedTaskId,
+                                    tx,
+                                    animBounds
+                            )
                         }
                         addListener(object : AnimatorListenerAdapter() {
                             override fun onAnimationEnd(animation: Animator) {
-                                state.windowDecoration.hideResizeVeil()
+                                onTaskResizeAnimationListener.onAnimationEnd(state.draggedTaskId)
                                 startTransitionFinishCb.onTransitionFinished(null /* null */)
                                 clearState()
                             }
@@ -576,7 +582,6 @@
     sealed class TransitionState {
         abstract val draggedTaskId: Int
         abstract val dragAnimator: MoveToDesktopAnimator
-        abstract val windowDecoration: DesktopModeWindowDecoration
         abstract val startTransitionToken: IBinder
         abstract var startTransitionFinishCb: Transitions.TransitionFinishCallback?
         abstract var startTransitionFinishTransaction: SurfaceControl.Transaction?
@@ -589,7 +594,6 @@
         data class FromFullscreen(
                 override val draggedTaskId: Int,
                 override val dragAnimator: MoveToDesktopAnimator,
-                override val windowDecoration: DesktopModeWindowDecoration,
                 override val startTransitionToken: IBinder,
                 override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
                 override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
@@ -603,7 +607,6 @@
         data class FromSplit(
                 override val draggedTaskId: Int,
                 override val dragAnimator: MoveToDesktopAnimator,
-                override val windowDecoration: DesktopModeWindowDecoration,
                 override val startTransitionToken: IBinder,
                 override var startTransitionFinishCb: Transitions.TransitionFinishCallback? = null,
                 override var startTransitionFinishTransaction: SurfaceControl.Transaction? = null,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 605600f..07cf202 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -38,7 +38,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration;
+import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -59,8 +59,8 @@
     public static final int FREEFORM_ANIMATION_DURATION = 336;
 
     private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
-    private DesktopModeWindowDecoration mDesktopModeWindowDecoration;
 
+    private OnTaskResizeAnimationListener mOnTaskResizeAnimationListener;
     public EnterDesktopTaskTransitionHandler(
             Transitions transitions) {
         this(transitions, SurfaceControl.Transaction::new);
@@ -73,14 +73,15 @@
         mTransactionSupplier = supplier;
     }
 
+    void setOnTaskResizeAnimationListener(OnTaskResizeAnimationListener listener) {
+        mOnTaskResizeAnimationListener =  listener;
+    }
+
     /**
      * Starts Transition of type TRANSIT_MOVE_TO_DESKTOP
      * @param wct WindowContainerTransaction for transition
-     * @param decor {@link DesktopModeWindowDecoration} of task being animated
      */
-    public void moveToDesktop(@NonNull WindowContainerTransaction wct,
-            DesktopModeWindowDecoration decor) {
-        mDesktopModeWindowDecoration = decor;
+    public void moveToDesktop(@NonNull WindowContainerTransaction wct) {
         final IBinder token = mTransitions.startTransition(TRANSIT_MOVE_TO_DESKTOP, wct, this);
         mPendingTransitionTokens.add(token);
     }
@@ -136,33 +137,33 @@
             @NonNull TransitionInfo.Change change,
             @NonNull SurfaceControl.Transaction startT,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        if (mDesktopModeWindowDecoration == null) {
-            Slog.e(TAG, "Window Decoration is not available for this transition");
+        final SurfaceControl leash = change.getLeash();
+        final Rect startBounds = change.getStartAbsBounds();
+        final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+        if (mOnTaskResizeAnimationListener == null) {
+            Slog.e(TAG, "onTaskResizeAnimationListener is not available for this transition");
             return false;
         }
 
-        final SurfaceControl leash = change.getLeash();
-        final Rect startBounds = change.getStartAbsBounds();
-        startT.setPosition(leash, startBounds.left, startBounds.right)
+        startT.setPosition(leash, startBounds.left, startBounds.top)
                 .setWindowCrop(leash, startBounds.width(), startBounds.height())
                 .show(leash);
-        mDesktopModeWindowDecoration.showResizeVeil(startT, startBounds);
-
+        mOnTaskResizeAnimationListener.onAnimationStart(taskInfo.taskId, startT, startBounds);
         final ValueAnimator animator = ValueAnimator.ofObject(new RectEvaluator(),
                 change.getStartAbsBounds(), change.getEndAbsBounds());
         animator.setDuration(FREEFORM_ANIMATION_DURATION);
         SurfaceControl.Transaction t = mTransactionSupplier.get();
         animator.addUpdateListener(animation -> {
             final Rect animationValue = (Rect) animator.getAnimatedValue();
-            t.setPosition(leash, animationValue.left, animationValue.right)
+            t.setPosition(leash, animationValue.left, animationValue.top)
                     .setWindowCrop(leash, animationValue.width(), animationValue.height())
                     .show(leash);
-            mDesktopModeWindowDecoration.updateResizeVeil(t, animationValue);
+            mOnTaskResizeAnimationListener.onBoundsChange(taskInfo.taskId, t, animationValue);
         });
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                mDesktopModeWindowDecoration.hideResizeVeil();
+                mOnTaskResizeAnimationListener.onAnimationEnd(taskInfo.taskId);
                 mTransitions.getMainExecutor().execute(
                         () -> finishCallback.onTransitionFinished(null));
             }
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 0218493..c469e65 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
@@ -21,7 +21,6 @@
 import android.animation.ValueAnimator
 import android.graphics.Rect
 import android.os.IBinder
-import android.util.SparseArray
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.window.TransitionInfo
@@ -30,7 +29,7 @@
 import androidx.core.animation.addListener
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
+import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
 import java.util.function.Supplier
 
 /** Handles the animation of quick resizing of desktop tasks. */
@@ -40,7 +39,7 @@
 ) : Transitions.TransitionHandler {
 
     private val rectEvaluator = RectEvaluator(Rect())
-    private val taskToDecorationMap = SparseArray<DesktopModeWindowDecoration>()
+    private lateinit var onTaskResizeAnimationListener: OnTaskResizeAnimationListener
 
     private var boundsAnimator: Animator? = null
 
@@ -49,13 +48,12 @@
     ) : this(transitions, Supplier { SurfaceControl.Transaction() })
 
     /** Starts a quick resize transition. */
-    fun startTransition(
-        wct: WindowContainerTransaction,
-        taskId: Int,
-        windowDecoration: DesktopModeWindowDecoration
-    ) {
+    fun startTransition(wct: WindowContainerTransaction) {
         transitions.startTransition(TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE, wct, this)
-        taskToDecorationMap.put(taskId, windowDecoration)
+    }
+
+    fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) {
+        onTaskResizeAnimationListener = listener
     }
 
     override fun startAnimation(
@@ -70,9 +68,6 @@
         val taskId = checkNotNull(change.taskInfo).taskId
         val startBounds = change.startAbsBounds
         val endBounds = change.endAbsBounds
-        val windowDecor =
-            taskToDecorationMap.removeReturnOld(taskId)
-                ?: throw IllegalStateException("Window decoration not found for task $taskId")
 
         val tx = transactionSupplier.get()
         boundsAnimator?.cancel()
@@ -90,7 +85,11 @@
                                 )
                                 .setWindowCrop(leash, startBounds.width(), startBounds.height())
                                 .show(leash)
-                            windowDecor.showResizeVeil(startTransaction, startBounds)
+                            onTaskResizeAnimationListener.onAnimationStart(
+                                    taskId,
+                                    startTransaction,
+                                    startBounds
+                            )
                         },
                         onEnd = {
                             finishTransaction
@@ -101,7 +100,7 @@
                                 )
                                 .setWindowCrop(leash, endBounds.width(), endBounds.height())
                                 .show(leash)
-                            windowDecor.hideResizeVeil()
+                            onTaskResizeAnimationListener.onAnimationEnd(taskId)
                             finishCallback.onTransitionFinished(null)
                             boundsAnimator = null
                         }
@@ -111,7 +110,7 @@
                         tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat())
                             .setWindowCrop(leash, rect.width(), rect.height())
                             .show(leash)
-                        windowDecor.updateResizeVeil(tx, rect)
+                        onTaskResizeAnimationListener.onBoundsChange(taskId, tx, rect)
                     }
                     start()
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/UnhandledDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/UnhandledDragController.kt
new file mode 100644
index 0000000..ccf48d0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/UnhandledDragController.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.draganddrop
+
+import android.os.RemoteException
+import android.util.Log
+import android.view.DragEvent
+import android.view.IWindowManager
+import android.window.IUnhandledDragCallback
+import android.window.IUnhandledDragListener
+import androidx.annotation.VisibleForTesting
+import com.android.internal.protolog.common.ProtoLog
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.protolog.ShellProtoLogGroup
+import java.util.function.Consumer
+
+/**
+ * Manages the listener and callbacks for unhandled global drags.
+ */
+class UnhandledDragController(
+    val wmService: IWindowManager,
+    mainExecutor: ShellExecutor
+) {
+    private var callback: UnhandledDragAndDropCallback? = null
+
+    private val unhandledDragListener: IUnhandledDragListener =
+        object : IUnhandledDragListener.Stub() {
+            override fun onUnhandledDrop(event: DragEvent, callback: IUnhandledDragCallback) {
+                mainExecutor.execute() {
+                    this@UnhandledDragController.onUnhandledDrop(event, callback)
+                }
+            }
+        }
+
+    /**
+     * Listener called when an unhandled drag is started.
+     */
+    interface UnhandledDragAndDropCallback {
+        /**
+         * Called when a global drag is unhandled (ie. dropped outside of all visible windows, or
+         * dropped on a window that does not want to handle it).
+         *
+         * The implementer _must_ call onFinishedCallback, and if it consumes the drop, then it is
+         * also responsible for releasing up the drag surface provided via the drag event.
+         */
+        fun onUnhandledDrop(dragEvent: DragEvent, onFinishedCallback: Consumer<Boolean>) {}
+    }
+
+    /**
+     * Sets a listener for callbacks when an unhandled drag happens.
+     */
+    fun setListener(listener: UnhandledDragAndDropCallback?) {
+        val updateWm = (callback == null && listener != null)
+                || (callback != null && listener == null)
+        callback = listener
+        if (updateWm) {
+            try {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+                    "%s unhandled drag listener",
+                    if (callback != null) "Registering" else "Unregistering")
+                wmService.setUnhandledDragListener(
+                    if (callback != null) unhandledDragListener else null)
+            } catch (e: RemoteException) {
+                Log.e(TAG, "Failed to set unhandled drag listener")
+            }
+        }
+    }
+
+    @VisibleForTesting
+    fun onUnhandledDrop(dragEvent: DragEvent, wmCallback: IUnhandledDragCallback) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+            "onUnhandledDrop: %s", dragEvent)
+        if (callback == null) {
+            wmCallback.notifyUnhandledDropComplete(false)
+        }
+
+        callback?.onUnhandledDrop(dragEvent) {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+                "Notifying onUnhandledDrop complete: %b", it)
+            wmCallback.notifyUnhandledDropComplete(it)
+        }
+    }
+
+    companion object {
+        private val TAG = UnhandledDragController::class.java.simpleName
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index 93d7636..196e04e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -480,7 +480,7 @@
         WindowContainerTransaction wct = new WindowContainerTransaction();
         if (mCaptionInsets != null) {
             wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
-                    WindowInsets.Type.captionBar(), mCaptionInsets);
+                    WindowInsets.Type.captionBar(), mCaptionInsets, null /* boundingRects */);
         } else {
             wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0,
                     WindowInsets.Type.captionBar());
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 891eea0..7db3d38 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
@@ -211,6 +211,8 @@
         mShellCommandHandler.addDumpCallback(this::dump, this);
         mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
                 new DesktopModeOnInsetsChangedListener());
+        mDesktopTasksController.ifPresent(c -> c.setOnTaskResizeAnimationListener(
+                new DeskopModeOnTaskResizeAnimationListener()));
     }
 
     @Override
@@ -356,7 +358,7 @@
                     // App sometimes draws before the insets from WindowDecoration#relayout have
                     // been added, so they must be added here
                     mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
-                    mDesktopTasksController.get().moveToDesktop(decoration, mTaskId, wct);
+                    mDesktopTasksController.get().moveToDesktop(mTaskId, wct);
                     closeOtherSplitTask(mTaskId);
                 }
                 decoration.closeHandleMenu();
@@ -387,25 +389,23 @@
                     return;
                 }
                 final RunningTaskInfo taskInfo = decoration.mTaskInfo;
-                mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(
-                        taskInfo, decoration));
+                mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo));
                 decoration.closeHandleMenu();
             } else if (id == R.id.maximize_menu_maximize_button) {
                 final RunningTaskInfo taskInfo = decoration.mTaskInfo;
-                mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(
-                        taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId)));
+                mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo));
                 decoration.closeHandleMenu();
                 decoration.closeMaximizeMenu();
             } else if (id == R.id.maximize_menu_snap_left_button) {
                 final RunningTaskInfo taskInfo = decoration.mTaskInfo;
                 mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
-                        taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId), SnapPosition.LEFT));
+                        taskInfo, SnapPosition.LEFT));
                 decoration.closeHandleMenu();
                 decoration.closeMaximizeMenu();
             } else if (id == R.id.maximize_menu_snap_right_button) {
                 final RunningTaskInfo taskInfo = decoration.mTaskInfo;
                 mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
-                        taskInfo, mWindowDecorByTaskId.get(taskInfo.taskId), SnapPosition.RIGHT));
+                        taskInfo, SnapPosition.RIGHT));
                 decoration.closeHandleMenu();
                 decoration.closeMaximizeMenu();
             }
@@ -558,7 +558,7 @@
             }
             mDesktopTasksController.ifPresent(c -> {
                 final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
-                c.toggleDesktopTaskSize(decoration.mTaskInfo, decoration);
+                c.toggleDesktopTaskSize(decoration.mTaskInfo);
             });
             return true;
         }
@@ -761,7 +761,7 @@
                                     relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
                             mDesktopTasksController.ifPresent(
                                     c -> c.startDragToDesktop(relevantDecor.mTaskInfo,
-                                            mMoveToDesktopAnimator, relevantDecor));
+                                            mMoveToDesktopAnimator));
                         }
                     }
                     if (mMoveToDesktopAnimator != null) {
@@ -1020,6 +1020,34 @@
         pw.println(innerPrefix + "mWindowDecorByTaskId=" + mWindowDecorByTaskId);
     }
 
+    private class DeskopModeOnTaskResizeAnimationListener
+            implements OnTaskResizeAnimationListener {
+        @Override
+        public void onAnimationStart(int taskId, Transaction t, Rect bounds) {
+            final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+            if (decoration == null)  {
+                t.apply();
+                return;
+            }
+            decoration.showResizeVeil(t, bounds);
+        }
+
+        @Override
+        public void onBoundsChange(int taskId, Transaction t, Rect bounds) {
+            final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+            if (decoration == null) return;
+            decoration.updateResizeVeil(t, bounds);
+        }
+
+        @Override
+        public void onAnimationEnd(int taskId) {
+            final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+            if (decoration == null) return;
+            decoration.hideResizeVeil();
+        }
+    }
+
+
     private class DragStartListenerImpl
             implements DragPositioningCallbackUtility.DragStartListener {
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskResizeAnimationListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskResizeAnimationListener.kt
new file mode 100644
index 0000000..09c62bf
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/OnTaskResizeAnimationListener.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.graphics.Rect
+import android.view.SurfaceControl
+
+import com.android.wm.shell.transition.Transitions.TransitionHandler
+/**
+ * Listener that allows implementations of [TransitionHandler] to notify when an
+ * animation that is resizing a task is starting, updating, and finishing the animation.
+ */
+interface OnTaskResizeAnimationListener {
+    /**
+     * Notifies that a transition animation is about to be started with the given bounds.
+     */
+    fun onAnimationStart(taskId: Int, t: SurfaceControl.Transaction, bounds: Rect)
+
+    /**
+     * Notifies that a transition animation is expanding or shrinking the task to the given bounds.
+     */
+    fun onBoundsChange(taskId: Int, t: SurfaceControl.Transaction, bounds: Rect)
+
+    /**
+     * Notifies that a transition animation is about to be finished.
+     */
+    fun onAnimationEnd(taskId: Int)
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index afe837e..35d5940 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -298,10 +298,11 @@
                 mCaptionInsetsRect.bottom =
                         mCaptionInsetsRect.top + outResult.mCaptionHeight;
                 wct.addInsetsSource(mTaskInfo.token,
-                        mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
+                        mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect,
+                        null /* boundingRects */);
                 wct.addInsetsSource(mTaskInfo.token,
                         mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
-                        mCaptionInsetsRect);
+                        mCaptionInsetsRect, null /* boundingRects */);
             } else {
                 wct.removeInsetsSource(mTaskInfo.token, mOwner, 0 /* index */,
                         WindowInsets.Type.captionBar());
@@ -546,7 +547,7 @@
         final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId);
         final Rect captionInsets = new Rect(0, 0, 0, captionHeight);
         wct.addInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, WindowInsets.Type.captionBar(),
-                captionInsets);
+                captionInsets, null /* boundingRects */);
     }
 
     static class RelayoutParams {
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 9249b0a..79634e6 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
@@ -303,7 +303,7 @@
     fun moveToDesktop_displayFullscreen_windowingModeSetToFreeform() {
         val task = setUpFullscreenTask()
         task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FULLSCREEN
-        controller.moveToDesktop(desktopModeWindowDecoration, task)
+        controller.moveToDesktop(task)
         val wct = getLatestMoveToDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
             .isEqualTo(WINDOWING_MODE_FREEFORM)
@@ -313,7 +313,7 @@
     fun moveToDesktop_displayFreeform_windowingModeSetToUndefined() {
         val task = setUpFullscreenTask()
         task.configuration.windowConfiguration.displayWindowingMode = WINDOWING_MODE_FREEFORM
-        controller.moveToDesktop(desktopModeWindowDecoration, task)
+        controller.moveToDesktop(task)
         val wct = getLatestMoveToDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
                 .isEqualTo(WINDOWING_MODE_UNDEFINED)
@@ -321,7 +321,7 @@
 
     @Test
     fun moveToDesktop_nonExistentTask_doesNothing() {
-        controller.moveToDesktop(desktopModeWindowDecoration, 999)
+        controller.moveToDesktop(999)
         verifyWCTNotExecuted()
     }
 
@@ -332,7 +332,7 @@
         val fullscreenTask = setUpFullscreenTask()
         markTaskHidden(freeformTask)
 
-        controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTask)
+        controller.moveToDesktop(fullscreenTask)
 
         with(getLatestMoveToDesktopWct()) {
             // Operations should include home task, freeform task
@@ -354,7 +354,7 @@
         val freeformTaskSecond = setUpFreeformTask(displayId = SECOND_DISPLAY)
         markTaskHidden(freeformTaskSecond)
 
-        controller.moveToDesktop(desktopModeWindowDecoration, fullscreenTaskDefault)
+        controller.moveToDesktop(fullscreenTaskDefault)
 
         with(getLatestMoveToDesktopWct()) {
             // Check that hierarchy operations do not include tasks from second display
@@ -368,7 +368,7 @@
     @Test
     fun moveToDesktop_splitTaskExitsSplit() {
         val task = setUpSplitScreenTask()
-        controller.moveToDesktop(desktopModeWindowDecoration, task)
+        controller.moveToDesktop(task)
         val wct = getLatestMoveToDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
             .isEqualTo(WINDOWING_MODE_FREEFORM)
@@ -380,7 +380,7 @@
     @Test
     fun moveToDesktop_fullscreenTaskDoesNotExitSplit() {
         val task = setUpFullscreenTask()
-        controller.moveToDesktop(desktopModeWindowDecoration, task)
+        controller.moveToDesktop(task)
         val wct = getLatestMoveToDesktopWct()
         assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
             .isEqualTo(WINDOWING_MODE_FREEFORM)
@@ -802,7 +802,7 @@
     private fun getLatestMoveToDesktopWct(): WindowContainerTransaction {
         val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
         if (ENABLE_SHELL_TRANSITIONS) {
-            verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture(), any())
+            verify(enterDesktopTransitionHandler).moveToDesktop(arg.capture())
         } else {
             verify(shellTaskOrganizer).applyTransaction(arg.capture())
         }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index be639e8..98e90d6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -24,7 +24,6 @@
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP
 import com.android.wm.shell.windowdecor.MoveToDesktopAnimator
-import java.util.function.Supplier
 import junit.framework.Assert.assertFalse
 import org.junit.Before
 import org.junit.Test
@@ -38,6 +37,7 @@
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.verifyZeroInteractions
 import org.mockito.kotlin.whenever
+import java.util.function.Supplier
 
 /** Tests of [DragToDesktopTransitionHandler]. */
 @SmallTest
@@ -246,7 +246,7 @@
                 )
             )
             .thenReturn(token)
-        handler.startDragToDesktopTransition(task.taskId, dragAnimator, mock())
+        handler.startDragToDesktopTransition(task.taskId, dragAnimator)
         return token
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/UnhandledDragControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/UnhandledDragControllerTest.kt
new file mode 100644
index 0000000..522f052
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/UnhandledDragControllerTest.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.draganddrop
+
+import android.os.RemoteException
+import android.view.DragEvent
+import android.view.DragEvent.ACTION_DROP
+import android.view.IWindowManager
+import android.view.SurfaceControl
+import android.window.IUnhandledDragCallback
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.draganddrop.UnhandledDragController.UnhandledDragAndDropCallback
+import java.util.function.Consumer
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for the unhandled drag controller.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UnhandledDragControllerTest : ShellTestCase() {
+    @Mock
+    private lateinit var mIWindowManager: IWindowManager
+
+    @Mock
+    private lateinit var mMainExecutor: ShellExecutor
+
+    private lateinit var mController: UnhandledDragController
+
+    @Before
+    @Throws(RemoteException::class)
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        mController = UnhandledDragController(mIWindowManager, mMainExecutor)
+    }
+
+    @Test
+    fun setListener_registersUnregistersWithWM() {
+        mController.setListener(object : UnhandledDragAndDropCallback {})
+        mController.setListener(object : UnhandledDragAndDropCallback {})
+        mController.setListener(object : UnhandledDragAndDropCallback {})
+        verify(mIWindowManager, Mockito.times(1))
+                .setUnhandledDragListener(ArgumentMatchers.any())
+
+        reset(mIWindowManager)
+        mController.setListener(null)
+        mController.setListener(null)
+        mController.setListener(null)
+        verify(mIWindowManager, Mockito.times(1))
+                .setUnhandledDragListener(ArgumentMatchers.isNull())
+    }
+
+    @Test
+    fun onUnhandledDrop_noListener_expectNotifyUnhandled() {
+        // Simulate an unhandled drop
+        val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null,
+            null, null, false)
+        val wmCallback = mock(IUnhandledDragCallback::class.java)
+        mController.onUnhandledDrop(dropEvent, wmCallback)
+
+        verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(false))
+    }
+
+    @Test
+    fun onUnhandledDrop_withListener_expectNotifyHandled() {
+        val lastDragEvent = arrayOfNulls<DragEvent>(1)
+
+        // Set a listener to listen for unhandled drops
+        mController.setListener(object : UnhandledDragAndDropCallback {
+            override fun onUnhandledDrop(dragEvent: DragEvent,
+                onFinishedCallback: Consumer<Boolean>) {
+                lastDragEvent[0] = dragEvent
+                onFinishedCallback.accept(true)
+                dragEvent.dragSurface.release()
+            }
+        })
+
+        // Simulate an unhandled drop
+        val dragSurface = mock(SurfaceControl::class.java)
+        val dropEvent = DragEvent.obtain(ACTION_DROP, 0f, 0f, 0f, 0f, null, null, null,
+            dragSurface, null, false)
+        val wmCallback = mock(IUnhandledDragCallback::class.java)
+        mController.onUnhandledDrop(dropEvent, wmCallback)
+
+        verify(wmCallback).notifyUnhandledDropComplete(ArgumentMatchers.eq(true))
+        verify(dragSurface).release()
+        assertEquals(lastDragEvent.get(0), dropEvent)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 7b53f70..228b25c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -259,7 +259,8 @@
                     any(),
                     eq(0 /* index */),
                     eq(WindowInsets.Type.captionBar()),
-                    eq(new Rect(100, 300, 400, 364)));
+                    eq(new Rect(100, 300, 400, 364)),
+                    any());
         }
 
         verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
@@ -569,9 +570,9 @@
         windowDecor.relayout(taskInfo);
 
         verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
-                eq(0) /* index */, eq(captionBar()), any());
+                eq(0) /* index */, eq(captionBar()), any(), any());
         verify(mMockWindowContainerTransaction).addInsetsSource(eq(taskInfo.token), any(),
-                eq(0) /* index */, eq(mandatorySystemGestures()), any());
+                eq(0) /* index */, eq(mandatorySystemGestures()), any(), any());
     }
 
     @Test
diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java
index 2de8eef..ab5c54b 100644
--- a/media/java/android/media/AudioDevicePort.java
+++ b/media/java/android/media/AudioDevicePort.java
@@ -33,7 +33,7 @@
  * device at the boundary of the audio system.
  * In addition to base audio port attributes, the device descriptor contains:
  * - the device type (e.g AudioManager.DEVICE_OUT_SPEAKER)
- * - the device address (e.g MAC adddress for AD2P sink).
+ * - the device address (e.g MAC address for AD2P sink).
  * @see AudioPort
  * @hide
  */
diff --git a/media/java/android/media/AudioHalVersionInfo.java b/media/java/android/media/AudioHalVersionInfo.java
index 4cdfc51..2b6f72e 100644
--- a/media/java/android/media/AudioHalVersionInfo.java
+++ b/media/java/android/media/AudioHalVersionInfo.java
@@ -79,6 +79,9 @@
     /**
      * List of all valid Audio HAL versions. This list need to be in sync with sAudioHALVersions
      * defined in frameworks/av/media/libaudiohal/FactoryHal.cpp.
+     *
+     * Note: update {@link android.media.audio.cts.AudioHalVersionInfoTest} CTS accordingly if
+     * there is a change to supported versions.
      */
     public static final @NonNull List<AudioHalVersionInfo> VERSIONS =
             List.of(AIDL_1_0, HIDL_7_1, HIDL_7_0, HIDL_6_0, HIDL_5_0);
diff --git a/media/java/android/media/flags/projection.aconfig b/media/java/android/media/flags/projection.aconfig
new file mode 100644
index 0000000..c4b38c7
--- /dev/null
+++ b/media/java/android/media/flags/projection.aconfig
@@ -0,0 +1,11 @@
+package: "com.android.media.flags"
+
+# Project link: https://gantry.corp.google.com/projects/android_platform_window_surfaces/changes
+
+flag {
+    name: "limit_manage_media_projection"
+    namespace: "lse_desktop_experience"
+    description: "Limit signature permission manage_media_projection to the SystemUI role"
+    bug: "323008518"
+    is_fixed_read_only: true
+}
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index e3290d6..2a0648d 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -180,7 +180,7 @@
     @SuppressLint("UnflaggedApi")
     @TestApi
     @NonNull
-    public Intent createScreenCaptureIntent(@Nullable LaunchCookie launchCookie) {
+    public Intent createScreenCaptureIntent(@NonNull LaunchCookie launchCookie) {
         Intent i = createScreenCaptureIntent();
         i.putExtra(EXTRA_LAUNCH_COOKIE, launchCookie);
         return i;
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 59b10c6..76664a6 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringDef;
@@ -59,7 +60,7 @@
  */
 @FlaggedApi(Flags.FLAG_ENABLE_AD_SERVICE_FW)
 @SystemService(Context.TV_AD_SERVICE)
-public class TvAdManager {
+public final class TvAdManager {
     // TODO: implement more methods and unhide APIs.
     private static final String TAG = "TvAdManager";
 
@@ -237,6 +238,76 @@
      */
     public static final String SESSION_DATA_KEY_REQUEST_ID = "request_id";
 
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = false, prefix = "SESSION_STATE_", value = {
+            SESSION_STATE_STOPPED,
+            SESSION_STATE_RUNNING,
+            SESSION_STATE_ERROR})
+    public @interface SessionState {}
+
+    /**
+     * Stopped (or not started) state of AD service session.
+     */
+    public static final int SESSION_STATE_STOPPED = 1;
+    /**
+     * Running state of AD service session.
+     */
+    public static final int SESSION_STATE_RUNNING = 2;
+    /**
+     * Error state of AD service session.
+     */
+    public static final int SESSION_STATE_ERROR = 3;
+
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = false, prefix = "ERROR_", value = {
+            ERROR_NONE,
+            ERROR_UNKNOWN,
+            ERROR_NOT_SUPPORTED,
+            ERROR_WEAK_SIGNAL,
+            ERROR_RESOURCE_UNAVAILABLE,
+            ERROR_BLOCKED,
+            ERROR_ENCRYPTED,
+            ERROR_UNKNOWN_CHANNEL,
+    })
+    public @interface ErrorCode {}
+
+    /**
+     * No error.
+     */
+    public static final int ERROR_NONE = 0;
+    /**
+     * Unknown error code.
+     */
+    public static final int ERROR_UNKNOWN = 1;
+    /**
+     * Error code for an unsupported channel.
+     */
+    public static final int ERROR_NOT_SUPPORTED = 2;
+    /**
+     * Error code for weak signal.
+     */
+    public static final int ERROR_WEAK_SIGNAL = 3;
+    /**
+     * Error code when resource (e.g. tuner) is unavailable.
+     */
+    public static final int ERROR_RESOURCE_UNAVAILABLE = 4;
+    /**
+     * Error code for blocked contents.
+     */
+    public static final int ERROR_BLOCKED = 5;
+    /**
+     * Error code when the key or module is missing for the encrypted channel.
+     */
+    public static final int ERROR_ENCRYPTED = 6;
+    /**
+     * Error code when the current channel is an unknown channel.
+     */
+    public static final int ERROR_UNKNOWN_CHANNEL = 7;
+
     private final ITvAdManager mService;
     private final int mUserId;
 
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 6c8a8fd..2bba0f3 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -280,7 +280,6 @@
 
         /**
          * Requests the bounds of the current video.
-         * @hide
          */
         @CallSuper
         public void requestCurrentVideoBounds() {
@@ -304,7 +303,6 @@
 
         /**
          * Requests the URI of the current channel.
-         * @hide
          */
         @CallSuper
         public void requestCurrentChannelUri() {
@@ -328,7 +326,6 @@
 
         /**
          * Requests the list of {@link TvTrackInfo}.
-         * @hide
          */
         @CallSuper
         public void requestTrackInfoList() {
@@ -354,7 +351,6 @@
          * Requests current TV input ID.
          *
          * @see android.media.tv.TvInputInfo
-         * @hide
          */
         @CallSuper
         public void requestCurrentTvInputId() {
@@ -393,7 +389,6 @@
          * @param data the original bytes to be signed.
          *
          * @see #onSigningResult(String, byte[])
-         * @hide
          */
         @CallSuper
         public void requestSigning(@NonNull String signingId, @NonNull String algorithm,
@@ -535,28 +530,24 @@
          * Receives current video bounds.
          *
          * @param bounds the rectangle area for rendering the current video.
-         * @hide
          */
         public void onCurrentVideoBounds(@NonNull Rect bounds) {
         }
 
         /**
          * Receives current channel URI.
-         * @hide
          */
         public void onCurrentChannelUri(@Nullable Uri channelUri) {
         }
 
         /**
          * Receives track list.
-         * @hide
          */
         public void onTrackInfoList(@NonNull List<TvTrackInfo> tracks) {
         }
 
         /**
          * Receives current TV input ID.
-         * @hide
          */
         public void onCurrentTvInputId(@Nullable String inputId) {
         }
@@ -569,7 +560,6 @@
          * @param result the signed result.
          *
          * @see #requestSigning(String, String, String, byte[])
-         * @hide
          */
         public void onSigningResult(@NonNull String signingId, @NonNull byte[] result) {
         }
@@ -584,7 +574,6 @@
          *     "onRequestSigning" can also be added to the params.
          *
          * @see TvAdView#ERROR_KEY_METHOD_NAME
-         * @hide
          */
         public void onError(@NonNull String errMsg, @NonNull Bundle params) {
         }
@@ -601,7 +590,6 @@
          *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
          *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
          *             how to parse this data.
-         * @hide
          */
         public void onTvMessage(@TvInputManager.TvMessageType int type,
                 @NonNull Bundle data) {
@@ -671,6 +659,30 @@
         }
 
         /**
+         * Notifies when the session state is changed.
+         *
+         * @param state the current session state.
+         * @param err the error code for error state. {@link TvAdManager#ERROR_NONE} is
+         *            used when the state is not {@link TvAdManager#SESSION_STATE_ERROR}.
+         */
+        @CallSuper
+        public void notifySessionStateChanged(
+                @TvAdManager.SessionState int state,
+                @TvAdManager.ErrorCode int err) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    if (DEBUG) {
+                        Log.d(TAG, "notifySessionStateChanged (state="
+                                + state + "; err=" + err + ")");
+                    }
+                    // TODO: handle session callback
+                }
+            });
+        }
+
+        /**
          * Takes care of dispatching incoming input events and tells whether the event was handled.
          */
         int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index ee01468..2fac8ce 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -61,10 +61,19 @@
      * The name of the method where the error happened, if applicable. For example, if there is an
      * error during signing, the request name is "onRequestSigning".
      * @see #notifyError(String, Bundle)
-     * @hide
      */
     public static final String ERROR_KEY_METHOD_NAME = "method_name";
 
+    /**
+     * The error code of an error.
+     *
+     * <p>It can be {@link TvAdManager#ERROR_WEAK_SIGNAL},
+     * {@link TvAdManager#ERROR_RESOURCE_UNAVAILABLE}, etc.
+     *
+     * @see #notifyError(String, Bundle)
+     */
+    public static final String ERROR_KEY_ERROR_CODE = "error_code";
+
     private final TvAdManager mTvAdManager;
 
     private final Handler mHandler = new Handler();
@@ -486,7 +495,6 @@
      * Sends current video bounds to related TV AD service.
      *
      * @param bounds the rectangle area for rendering the current video.
-     * @hide
      */
     public void sendCurrentVideoBounds(@NonNull Rect bounds) {
         if (DEBUG) {
@@ -502,7 +510,6 @@
      *
      * @param channelUri The current channel URI; {@code null} if there is no currently tuned
      *                   channel.
-     * @hide
      */
     public void sendCurrentChannelUri(@Nullable Uri channelUri) {
         if (DEBUG) {
@@ -515,7 +522,6 @@
 
     /**
      * Sends track info list to related TV AD service.
-     * @hide
      */
     public void sendTrackInfoList(@Nullable List<TvTrackInfo> tracks) {
         if (DEBUG) {
@@ -532,7 +538,6 @@
      * @param inputId The current TV input ID whose channel is tuned. {@code null} if no channel is
      *                tuned.
      * @see android.media.tv.TvInputInfo
-     * @hide
      */
     public void sendCurrentTvInputId(@Nullable String inputId) {
         if (DEBUG) {
@@ -553,7 +558,6 @@
      * @param signingId the ID to identify the request. It's the same as the corresponding ID in
      *        {@link TvAdService.Session#requestSigning(String, String, String, byte[])}
      * @param result the signed result.
-     * @hide
      */
     public void sendSigningResult(@NonNull String signingId, @NonNull byte[] result) {
         if (DEBUG) {
@@ -574,7 +578,7 @@
      *     can also be added to the params.
      *
      * @see #ERROR_KEY_METHOD_NAME
-     * @hide
+     * @see #ERROR_KEY_ERROR_CODE
      */
     public void notifyError(@NonNull String errMsg, @NonNull Bundle params) {
         if (DEBUG) {
@@ -597,7 +601,6 @@
      *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
      *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
      *             how to parse this data.
-     * @hide
      */
     public void notifyTvMessage(@NonNull @TvInputManager.TvMessageType int type,
             @NonNull Bundle data) {
@@ -633,7 +636,6 @@
      * @param callback the callback to receive events. MUST NOT be {@code null}.
      *
      * @see #clearCallback()
-     * @hide
      */
     public void setCallback(
             @NonNull @CallbackExecutor Executor executor,
@@ -649,7 +651,6 @@
      * Clears the callback.
      *
      * @see #setCallback(Executor, TvAdCallback)
-     * @hide
      */
     public void clearCallback() {
         synchronized (mCallbackLock) {
@@ -845,7 +846,6 @@
 
     /**
      * Callback used to receive various status updates on the {@link TvAdView}.
-     * @hide
      */
     public abstract static class TvAdCallback {
 
@@ -898,5 +898,20 @@
         public void onRequestSigning(@NonNull String serviceId, @NonNull String signingId,
                 @NonNull String algorithm, @NonNull String alias, @NonNull byte[] data) {
         }
+
+        /**
+         * This is called when the state of corresponding AD service is changed.
+         *
+         * @param serviceId The ID of the AD service bound to this view.
+         * @param state the current state.
+         * @param err the error code for error state. {@link TvAdManager#ERROR_NONE}
+         *              is used when the state is not
+         *              {@link TvAdManager#SESSION_STATE_ERROR}.
+         */
+        public void onStateChanged(
+                @NonNull String serviceId,
+                @TvAdManager.SessionState int state,
+                @TvAdManager.ErrorCode int err) {
+        }
     }
 }
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index eba26d4..f332f81 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -873,6 +873,9 @@
 
         /**
          * Called when the corresponding TV input selected to a track.
+         *
+         * If the track is deselected and no track is currently selected,
+         * trackId is an empty string.
          */
         public void onTrackSelected(@TvTrackInfo.Type int type, @NonNull String trackId) {
         }
@@ -1845,6 +1848,10 @@
             if (DEBUG) {
                 Log.d(TAG, "notifyTrackSelected (type=" + type + "trackId=" + trackId + ")");
             }
+            // TvInputService accepts a Null String, but onTrackSelected expects NonNull.
+            if (trackId == null) {
+                trackId = "";
+            }
             onTrackSelected(type, trackId);
         }
 
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
index 5590219..7cd6bb3 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/components/CredentialsScreenChip.kt
@@ -48,8 +48,6 @@
         {
             Text(
                 text = label,
-                modifier = Modifier.fillMaxWidth(),
-                textAlign = TextAlign.Center,
                 overflow = TextOverflow.Ellipsis,
                 maxLines = if (secondaryLabel != null) 1 else 2,
             )
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
index 7cc95c5..0fc1845 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
@@ -36,6 +36,7 @@
 import android.graphics.drawable.Icon
 import android.os.Build
 import android.os.Bundle
+import android.os.Flags
 import android.os.Process
 import android.os.UserHandle
 import android.os.UserManager
@@ -97,16 +98,17 @@
             }
         }
 
-        if (getMaxTargetSdkVersionForUid(context, callingUid) >= Build.VERSION_CODES.P
-            && !isPermissionGranted(
+        if (getMaxTargetSdkVersionForUid(context, callingUid) >= Build.VERSION_CODES.P &&
+            !isPermissionGranted(
                 context, Manifest.permission.REQUEST_DELETE_PACKAGES, callingUid
-            )
-            && !isPermissionGranted(context, Manifest.permission.DELETE_PACKAGES, callingUid)
+            ) &&
+            !isPermissionGranted(context, Manifest.permission.DELETE_PACKAGES, callingUid)
         ) {
             Log.e(
-                LOG_TAG, "Uid " + callingUid + " does not have "
-                    + Manifest.permission.REQUEST_DELETE_PACKAGES + " or "
-                    + Manifest.permission.DELETE_PACKAGES
+                LOG_TAG,
+                "Uid " + callingUid + " does not have " +
+                    Manifest.permission.REQUEST_DELETE_PACKAGES + " or " +
+                    Manifest.permission.DELETE_PACKAGES
             )
             return UninstallAborted(UninstallAborted.ABORT_REASON_GENERIC_ERROR)
         }
@@ -138,8 +140,9 @@
             val profiles = userManager!!.userProfiles
             if (!profiles.contains(uninstalledUser)) {
                 Log.e(
-                    LOG_TAG, "User " + Process.myUserHandle() + " can't request uninstall "
-                        + "for user " + uninstalledUser
+                    LOG_TAG,
+                    "User " + Process.myUserHandle() + " can't request uninstall " +
+                        "for user " + uninstalledUser
                 )
                 return UninstallAborted(UninstallAborted.ABORT_REASON_USER_NOT_ALLOWED)
             }
@@ -202,9 +205,13 @@
         val isSingleUser = isSingleUser()
 
         if (isUpdate) {
-            messageBuilder.append(context.getString(
-                    if (isSingleUser) R.string.uninstall_update_text
-                    else R.string.uninstall_update_text_multiuser
+            messageBuilder.append(
+                context.getString(
+                    if (isSingleUser) {
+                        R.string.uninstall_update_text
+                    } else {
+                        R.string.uninstall_update_text_multiuser
+                    }
                 )
             )
         } else if (uninstallFromAllUsers && !isSingleUser) {
@@ -214,42 +221,42 @@
             val customUserManager = context.createContextAsUser(uninstalledUser!!, 0)
                 .getSystemService(UserManager::class.java)
             val userName = customUserManager!!.userName
-
-            val uninstalledUserType = getUninstalledUserType(myUserHandle, uninstalledUser!!)
-            val messageString: String
-            when (uninstalledUserType) {
-                UserManager.USER_TYPE_PROFILE_MANAGED -> {
+            var messageString = context.getString(
+                    R.string.uninstall_application_text_user,
+                userName
+            )
+            if (userManager!!.isSameProfileGroup(myUserHandle, uninstalledUser!!)) {
+                if (customUserManager!!.isManagedProfile()) {
                     messageString = context.getString(
-                        R.string.uninstall_application_text_current_user_work_profile, userName
+                            R.string.uninstall_application_text_current_user_work_profile, userName
                     )
-                }
-
-                UserManager.USER_TYPE_PROFILE_CLONE -> {
+                } else if (customUserManager!!.isCloneProfile()){
                     isClonedApp = true
                     messageString = context.getString(
-                        R.string.uninstall_application_text_current_user_clone_profile
+                            R.string.uninstall_application_text_current_user_clone_profile
                     )
-                }
-
-                else -> {
+                } else if (Flags.allowPrivateProfile() && customUserManager!!.isPrivateProfile()) {
+                    // TODO(b/324244123): Get these Strings from a User Property API.
                     messageString = context.getString(
-                        R.string.uninstall_application_text_user, userName
+                            R.string.uninstall_application_text_current_user_private_profile
                     )
                 }
-
             }
             messageBuilder.append(messageString)
         } else if (isCloneProfile(uninstalledUser!!)) {
             isClonedApp = true
-            messageBuilder.append(context.getString(
+            messageBuilder.append(
+                context.getString(
                     R.string.uninstall_application_text_current_user_clone_profile
                 )
             )
-        } else if (myUserHandle == UserHandle.SYSTEM
-            && hasClonedInstance(targetAppInfo!!.packageName)
+        } else if (myUserHandle == UserHandle.SYSTEM &&
+            hasClonedInstance(targetAppInfo!!.packageName)
         ) {
-            messageBuilder.append(context.getString(
-                    R.string.uninstall_application_text_with_clone_instance, targetAppLabel
+            messageBuilder.append(
+                context.getString(
+                    R.string.uninstall_application_text_with_clone_instance,
+                    targetAppLabel
                 )
             )
         } else {
@@ -296,31 +303,6 @@
         return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2)
     }
 
-    /**
-     * Returns the type of the user from where an app is being uninstalled. We are concerned with
-     * only USER_TYPE_PROFILE_MANAGED and USER_TYPE_PROFILE_CLONE and whether the user and profile
-     * belong to the same profile group.
-     */
-    private fun getUninstalledUserType(
-        myUserHandle: UserHandle,
-        uninstalledUserHandle: UserHandle
-    ): String? {
-        if (!userManager!!.isSameProfileGroup(myUserHandle, uninstalledUserHandle)) {
-            return null
-        }
-        val customUserManager = context.createContextAsUser(uninstalledUserHandle, 0)
-            .getSystemService(UserManager::class.java)
-        val userTypes =
-            arrayOf(UserManager.USER_TYPE_PROFILE_MANAGED, UserManager.USER_TYPE_PROFILE_CLONE)
-
-        for (userType in userTypes) {
-            if (customUserManager!!.isUserOfType(userType)) {
-                return userType
-            }
-        }
-        return null
-    }
-
     private fun hasClonedInstance(packageName: String): Boolean {
         // Check if clone user is present on the device.
         var cloneUser: UserHandle? = null
@@ -334,8 +316,8 @@
         }
         // Check if another instance of given package exists in clone user profile.
         return try {
-            cloneUser != null
-                && packageManager.getPackageUidAsUser(
+            cloneUser != null &&
+                packageManager.getPackageUidAsUser(
                 packageName, PackageManager.PackageInfoFlags.of(0), cloneUser.identifier
                 ) > 0
         } catch (e: PackageManager.NameNotFoundException) {
@@ -382,7 +364,9 @@
         val storageStatsManager = context.getSystemService(StorageStatsManager::class.java)
         try {
             val stats = storageStatsManager!!.queryStatsForPackage(
-                packageManager.getApplicationInfo(pkg, 0).storageUuid, pkg, user
+                packageManager.getApplicationInfo(pkg, 0).storageUuid,
+                pkg,
+                user
             )
             return stats.getDataBytes()
         } catch (e: Exception) {
@@ -423,17 +407,24 @@
         broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId)
         broadcastIntent.setPackage(context.packageName)
         val pendingIntent = PendingIntent.getBroadcast(
-            context, uninstallId, broadcastIntent,
+            context,
+            uninstallId,
+            broadcastIntent,
             PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
         )
         if (!startUninstall(
-                targetPackageName!!, uninstalledUser!!, pendingIntent, uninstallFromAllUsers,
+                targetPackageName!!,
+                uninstalledUser!!,
+                pendingIntent,
+                uninstallFromAllUsers,
                 keepData
             )
         ) {
             handleUninstallResult(
                 PackageInstaller.STATUS_FAILURE,
-                PackageManager.DELETE_FAILED_INTERNAL_ERROR, null, 0
+                PackageManager.DELETE_FAILED_INTERNAL_ERROR,
+                null,
+                0
             )
         }
     }
@@ -474,9 +465,14 @@
 
         // Caller did not want the result back. So, we either show a Toast, or a Notification.
         if (status == PackageInstaller.STATUS_SUCCESS) {
-            val statusMessage = if (isClonedApp) context.getString(
-                R.string.uninstall_done_clone_app, targetAppLabel
-            ) else context.getString(R.string.uninstall_done_app, targetAppLabel)
+            val statusMessage = if (isClonedApp) {
+                context.getString(
+                R.string.uninstall_done_clone_app,
+                    targetAppLabel
+            )
+            } else {
+                context.getString(R.string.uninstall_done_app, targetAppLabel)
+            }
             uninstallResult.setValue(
                 UninstallSuccess(activityResultCode = legacyStatus, message = statusMessage)
             )
@@ -499,27 +495,32 @@
                         findUserOfDeviceAdmin(myUserHandle, targetPackageName!!)
                     if (otherBlockingUserHandle == null) {
                         Log.d(
-                            LOG_TAG, "Uninstall failed because $targetPackageName"
-                                + " is a device admin"
+                            LOG_TAG,
+                            "Uninstall failed because $targetPackageName" +
+                                " is a device admin"
                         )
                         addDeviceManagerButton(context, uninstallFailedNotification)
                         setBigText(
-                            uninstallFailedNotification, context.getString(
+                            uninstallFailedNotification,
+                            context.getString(
                                 R.string.uninstall_failed_device_policy_manager
                             )
                         )
                     } else {
                         Log.d(
-                            LOG_TAG, "Uninstall failed because $targetPackageName"
-                                + " is a device admin of user $otherBlockingUserHandle"
+                            LOG_TAG,
+                            "Uninstall failed because $targetPackageName" +
+                                " is a device admin of user $otherBlockingUserHandle"
                         )
                         val userName = context.createContextAsUser(otherBlockingUserHandle, 0)
                             .getSystemService(UserManager::class.java)!!.userName
                         setBigText(
-                            uninstallFailedNotification, String.format(
+                            uninstallFailedNotification,
+                            String.format(
                                 context.getString(
                                     R.string.uninstall_failed_device_policy_manager_of_user
-                                ), userName
+                                ),
+                                userName
                             )
                         )
                     }
@@ -528,7 +529,9 @@
                 PackageManager.DELETE_FAILED_OWNER_BLOCKED -> {
                     val otherBlockingUserHandle = findBlockingUser(targetPackageName!!)
                     val isProfileOfOrSame = isProfileOfOrSame(
-                        userManager!!, myUserHandle, otherBlockingUserHandle
+                        userManager!!,
+                        myUserHandle,
+                        otherBlockingUserHandle
                     )
                     if (isProfileOfOrSame) {
                         addDeviceManagerButton(context, uninstallFailedNotification)
@@ -538,15 +541,19 @@
                     var bigText: String? = null
                     if (otherBlockingUserHandle == null) {
                         Log.d(
-                            LOG_TAG, "Uninstall failed for $targetPackageName " +
+                            LOG_TAG,
+                            "Uninstall failed for $targetPackageName " +
                                 "with code $status no blocking user"
                         )
                     } else if (otherBlockingUserHandle === UserHandle.SYSTEM) {
                         bigText = context.getString(R.string.uninstall_blocked_device_owner)
                     } else {
                         bigText = context.getString(
-                            if (uninstallFromAllUsers) R.string.uninstall_all_blocked_profile_owner
-                            else R.string.uninstall_blocked_profile_owner
+                            if (uninstallFromAllUsers) {
+                                R.string.uninstall_all_blocked_profile_owner
+                            } else {
+                                R.string.uninstall_blocked_profile_owner
+                            }
                         )
                     }
                     bigText?.let { setBigText(uninstallFailedNotification, it) }
@@ -554,8 +561,9 @@
 
                 else -> {
                     Log.d(
-                        LOG_TAG, "Uninstall blocked for $targetPackageName"
-                            + " with legacy code $legacyStatus"
+                        LOG_TAG,
+                        "Uninstall blocked for $targetPackageName" +
+                            " with legacy code $legacyStatus"
                     )
                 }
             }
@@ -639,7 +647,9 @@
                 Icon.createWithResource(context, R.drawable.ic_settings_multiuser),
                 context.getString(R.string.manage_users),
                 PendingIntent.getActivity(
-                    context, 0, getUserSettingsIntent(),
+                    context,
+                    0,
+                    getUserSettingsIntent(),
                     PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
                 )
             )
@@ -668,7 +678,9 @@
                 Icon.createWithResource(context, R.drawable.ic_lock),
                 context.getString(R.string.manage_device_administrators),
                 PendingIntent.getActivity(
-                    context, 0, getDeviceManagerIntent(),
+                    context,
+                    0,
+                    getDeviceManagerIntent(),
                     PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
                 )
             )
@@ -706,7 +718,8 @@
             context.createContextAsUser(targetUser, 0)
                 .packageManager.packageInstaller.uninstall(
                     VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
-                    flags, pendingIntent.intentSender
+                    flags,
+                    pendingIntent.intentSender
                 )
             true
         } catch (e: IllegalArgumentException) {
@@ -719,7 +732,8 @@
         if (callback != null) {
             callback!!.onUninstallComplete(
                 targetPackageName!!,
-                PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user"
+                PackageManager.DELETE_FAILED_ABORTED,
+                "Cancelled by user"
             )
         }
     }
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 3252aad..1b29e83 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -526,7 +526,7 @@
     </string-array>
 
     <!-- USB configuration values for Developer Settings.
-         These are lists of USB functions passed to the USB Manager to change USB configuraton.
+         These are lists of USB functions passed to the USB Manager to change USB configuration.
          This can be overridden by devices with additional USB configurations.
          Do not translate. -->
     <string-array name="usb_configuration_values" translatable="false">
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 8ae117e..4feb6e6 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -247,6 +247,7 @@
         Settings.Secure.BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS,
         Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP,
         Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER,
+        Settings.Secure.CONTEXTUAL_SCREEN_TIMEOUT_ENABLED,
         Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
         Settings.Secure.HEARING_AID_RINGTONE_ROUTING,
         Settings.Secure.HEARING_AID_CALL_ROUTING,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 5adae375..d0ed656 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -397,6 +397,7 @@
         VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR);
         VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.LOCK_SCREEN_WEATHER_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.CONTEXTUAL_SCREEN_TIMEOUT_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.HEARING_AID_RINGTONE_ROUTING,
                 new DiscreteValueValidator(new String[] {"0", "1", "2"}));
         VALIDATORS.put(Secure.HEARING_AID_CALL_ROUTING,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 926e181..c086baa 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -862,6 +862,8 @@
 
     <!-- Permission required for CTS test - CtsTelephonyProviderTestCases -->
     <uses-permission android:name="android.permission.WRITE_APN_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
+    <uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
 
     <uses-permission android:name="android.permission.LOG_FOREGROUND_RESOURCE_USE" />
 
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING b/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING
index 2bd52b5..29a25ad 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING
+++ b/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING
@@ -4,8 +4,15 @@
       "name": "AccessibilityMenuServiceTests",
       "options": [
         {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
+          "exclude-annotation": "android.support.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "AccessibilityMenuServiceTests",
+      "options": [
         {
           "exclude-annotation": "android.support.test.filters.FlakyTest"
         }
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 9d1af0e..72c1092 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -36,6 +36,7 @@
 import android.app.KeyguardManager;
 import android.app.UiAutomation;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -57,6 +58,7 @@
 
 import org.junit.After;
 import org.junit.AfterClass;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -330,8 +332,10 @@
         AccessibilityNodeInfo assistantButton = findGridButtonInfo(getGridButtonList(),
                 String.valueOf(ShortcutId.ID_ASSISTANT_VALUE.ordinal()));
         Intent expectedIntent = new Intent(Intent.ACTION_VOICE_COMMAND);
-        String expectedPackage = expectedIntent.resolveActivity(
-                sInstrumentation.getContext().getPackageManager()).getPackageName();
+        ComponentName componentName = expectedIntent.resolveActivity(
+                sInstrumentation.getContext().getPackageManager());
+        Assume.assumeNotNull(componentName);
+        String expectedPackage = componentName.getPackageName();
 
         sUiAutomation.executeAndWaitForEvent(
                 () -> assistantButton.performAction(CLICK_ID),
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 56576f1..407873e 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -420,3 +420,12 @@
     }
 }
 
+flag {
+   name: "get_connected_device_name_unsynchronized"
+   namespace: "systemui"
+   description: "Decide whether to fetch the connected bluetooth device name outside a synchronized block."
+   bug: "323995015"
+   metadata {
+       purpose: PURPOSE_BUGFIX
+     }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeAwareExtensions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeAwareExtensions.kt
new file mode 100644
index 0000000..a7de1ee
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeAwareExtensions.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.scene.ui.composable
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Edge as ComposeAwareEdge
+import com.android.compose.animation.scene.SceneKey as ComposeAwareSceneKey
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.TransitionKey as ComposeAwareTransitionKey
+import com.android.compose.animation.scene.UserAction as ComposeAwareUserAction
+import com.android.compose.animation.scene.UserActionDistance as ComposeAwareUserActionDistance
+import com.android.compose.animation.scene.UserActionResult as ComposeAwareUserActionResult
+import com.android.systemui.scene.shared.model.Direction
+import com.android.systemui.scene.shared.model.Edge
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.TransitionKey
+import com.android.systemui.scene.shared.model.UserAction
+import com.android.systemui.scene.shared.model.UserActionDistance
+import com.android.systemui.scene.shared.model.UserActionResult
+
+// TODO(b/293899074): remove this file once we can use the types from SceneTransitionLayout.
+
+fun SceneKey.asComposeAware(): ComposeAwareSceneKey {
+    return ComposeAwareSceneKey(
+        debugName = toString(),
+        identity = this,
+    )
+}
+
+fun TransitionKey.asComposeAware(): ComposeAwareTransitionKey {
+    return ComposeAwareTransitionKey(
+        debugName = debugName,
+        identity = this,
+    )
+}
+
+fun UserAction.asComposeAware(): ComposeAwareUserAction {
+    return when (this) {
+        is UserAction.Swipe ->
+            Swipe(
+                pointerCount = pointerCount,
+                fromSource =
+                    when (this.fromEdge) {
+                        null -> null
+                        Edge.LEFT -> ComposeAwareEdge.Left
+                        Edge.TOP -> ComposeAwareEdge.Top
+                        Edge.RIGHT -> ComposeAwareEdge.Right
+                        Edge.BOTTOM -> ComposeAwareEdge.Bottom
+                    },
+                direction =
+                    when (this.direction) {
+                        Direction.LEFT -> SwipeDirection.Left
+                        Direction.UP -> SwipeDirection.Up
+                        Direction.RIGHT -> SwipeDirection.Right
+                        Direction.DOWN -> SwipeDirection.Down
+                    }
+            )
+        is UserAction.Back -> Back
+    }
+}
+
+fun UserActionResult.asComposeAware(): ComposeAwareUserActionResult {
+    val composeUnaware = this
+    return ComposeAwareUserActionResult(
+        toScene = composeUnaware.toScene.asComposeAware(),
+        transitionKey = composeUnaware.transitionKey?.asComposeAware(),
+        distance = composeUnaware.distance?.asComposeAware(),
+    )
+}
+
+fun UserActionDistance.asComposeAware(): ComposeAwareUserActionDistance {
+    val composeUnware = this
+    return object : ComposeAwareUserActionDistance {
+        override fun Density.absoluteDistance(
+            fromSceneSize: IntSize,
+            orientation: Orientation,
+        ): Float {
+            return composeUnware.absoluteDistance(
+                fromSceneWidth = fromSceneSize.width,
+                fromSceneHeight = fromSceneSize.height,
+                isHorizontal = orientation == Orientation.Horizontal,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeUnawareExtensions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeUnawareExtensions.kt
new file mode 100644
index 0000000..4c03664
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposeUnawareExtensions.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.composable
+
+import com.android.compose.animation.scene.ObservableTransitionState as ComposeAwareObservableTransitionState
+import com.android.compose.animation.scene.SceneKey as ComposeAwareSceneKey
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+
+fun ComposeAwareSceneKey.asComposeUnaware(): SceneKey {
+    return this.identity as SceneKey
+}
+
+fun ComposeAwareObservableTransitionState.asComposeUnaware(): ObservableTransitionState {
+    return when (this) {
+        is ComposeAwareObservableTransitionState.Idle ->
+            ObservableTransitionState.Idle(scene.asComposeUnaware())
+        is ComposeAwareObservableTransitionState.Transition ->
+            ObservableTransitionState.Transition(
+                fromScene = fromScene.asComposeUnaware(),
+                toScene = toScene.asComposeUnaware(),
+                progress = progress,
+                isInitiatedByUserInput = isInitiatedByUserInput,
+                isUserInputOngoing = isUserInputOngoing,
+            )
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
new file mode 100644
index 0000000..60c0b77
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.ui.composable
+
+import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.observableTransitionState
+import com.android.systemui.scene.shared.model.SceneDataSource
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.TransitionKey
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * An implementation of [SceneDataSource] that's backed by a [MutableSceneTransitionLayoutState].
+ */
+class SceneTransitionLayoutDataSource(
+    private val state: MutableSceneTransitionLayoutState,
+
+    /**
+     * The [CoroutineScope] of the @Composable that's using this, it's critical that this is *not*
+     * the application scope.
+     */
+    private val coroutineScope: CoroutineScope,
+) : SceneDataSource {
+    override val currentScene: StateFlow<SceneKey> =
+        state
+            .observableTransitionState()
+            .flatMapLatest { observableTransitionState ->
+                when (observableTransitionState) {
+                    is ObservableTransitionState.Idle -> flowOf(observableTransitionState.scene)
+                    is ObservableTransitionState.Transition ->
+                        observableTransitionState.isUserInputOngoing.map { isUserInputOngoing ->
+                            if (isUserInputOngoing) {
+                                observableTransitionState.fromScene
+                            } else {
+                                observableTransitionState.toScene
+                            }
+                        }
+                }
+            }
+            .map { it.asComposeUnaware() }
+            .stateIn(
+                scope = coroutineScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = state.transitionState.currentScene.asComposeUnaware(),
+            )
+
+    override fun changeScene(
+        toScene: SceneKey,
+        transitionKey: TransitionKey?,
+    ) {
+        state.setTargetScene(
+            targetScene = toScene.asComposeAware(),
+            transitionKey = transitionKey?.asComposeAware(),
+            coroutineScope = coroutineScope,
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 72e884e..9c2791f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -120,6 +120,7 @@
 internal fun promptInfo(
     logoRes: Int = -1,
     logoBitmap: Bitmap? = null,
+    logoDescription: String? = null,
     title: String = "title",
     subtitle: String = "sub",
     description: String = "desc",
@@ -132,6 +133,7 @@
     val info = PromptInfo()
     info.logoRes = logoRes
     info.logoBitmap = logoBitmap
+    info.logoDescription = logoDescription
     info.title = title
     info.subtitle = subtitle
     info.description = description
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
index 7242cb2..f6c0566 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
@@ -54,7 +54,7 @@
     private lateinit var powerInteractor: PowerInteractor
     private lateinit var underTest: LightRevealScrimRepositoryImpl
 
-    @get:Rule val animatorTestRule = AnimatorTestRule()
+    @get:Rule val animatorTestRule = AnimatorTestRule(this)
 
     @Before
     fun setUp() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index c23ec22..bf1d76f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -74,6 +74,8 @@
     private val dozeParameters = kosmos.dozeParameters
     private val underTest by lazy { kosmos.keyguardRootViewModel }
 
+    private val viewState = ViewStateAccessor()
+
     @Before
     fun setUp() {
         mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
@@ -251,7 +253,7 @@
     @Test
     fun alpha_idleOnHub_isZero() =
         testScope.runTest {
-            val alpha by collectLastValue(underTest.alpha)
+            val alpha by collectLastValue(underTest.alpha(viewState))
 
             // Hub transition state is idle with hub open.
             communalRepository.setTransitionState(
@@ -269,7 +271,7 @@
     @Test
     fun alpha_transitionToHub_isZero() =
         testScope.runTest {
-            val alpha by collectLastValue(underTest.alpha)
+            val alpha by collectLastValue(underTest.alpha(viewState))
 
             keyguardTransitionRepository.sendTransitionSteps(
                 from = KeyguardState.LOCKSCREEN,
@@ -283,7 +285,7 @@
     @Test
     fun alpha_transitionFromHubToLockscreen_isOne() =
         testScope.runTest {
-            val alpha by collectLastValue(underTest.alpha)
+            val alpha by collectLastValue(underTest.alpha(viewState))
 
             // Transition to the glanceable hub and back.
             keyguardTransitionRepository.sendTransitionSteps(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt
new file mode 100644
index 0000000..ed4b1e6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegatorTest.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.scene.shared.model
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.initialSceneKey
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SceneDataSourceDelegatorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val initialSceneKey = kosmos.initialSceneKey
+    private val fakeSceneDataSource = kosmos.fakeSceneDataSource
+
+    private val underTest = kosmos.sceneDataSourceDelegator
+
+    @Test
+    fun currentScene_withoutDelegate_startsWithInitialScene() =
+        testScope.runTest {
+            val currentScene by collectLastValue(underTest.currentScene)
+            underTest.setDelegate(null)
+
+            assertThat(currentScene).isEqualTo(initialSceneKey)
+        }
+
+    @Test
+    fun currentScene_withoutDelegate_doesNothing() =
+        testScope.runTest {
+            val currentScene by collectLastValue(underTest.currentScene)
+            underTest.setDelegate(null)
+            assertThat(currentScene).isNotEqualTo(SceneKey.Bouncer)
+
+            underTest.changeScene(toScene = SceneKey.Bouncer)
+
+            assertThat(currentScene).isEqualTo(initialSceneKey)
+        }
+
+    @Test
+    fun currentScene_withDelegate_startsWithInitialScene() =
+        testScope.runTest {
+            val currentScene by collectLastValue(underTest.currentScene)
+            assertThat(currentScene).isEqualTo(initialSceneKey)
+        }
+
+    @Test
+    fun currentScene_withDelegate_changesScenes() =
+        testScope.runTest {
+            val currentScene by collectLastValue(underTest.currentScene)
+            assertThat(currentScene).isNotEqualTo(SceneKey.Bouncer)
+
+            underTest.changeScene(toScene = SceneKey.Bouncer)
+
+            assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+        }
+
+    @Test
+    fun currentScene_reflectsDelegate() =
+        testScope.runTest {
+            val currentScene by collectLastValue(underTest.currentScene)
+
+            fakeSceneDataSource.changeScene(toScene = SceneKey.Bouncer)
+
+            assertThat(currentScene).isEqualTo(SceneKey.Bouncer)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
index 0c7ce97..288c083 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt
@@ -16,10 +16,12 @@
 
 package com.android.systemui.statusbar.notification.collection.render
 
+import android.os.Build
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.assertLogsWtf
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
 import com.android.systemui.statusbar.notification.collection.ListEntry
 import com.android.systemui.statusbar.notification.collection.NotifPipeline
@@ -30,7 +32,7 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
-import org.junit.Assert.assertThrows
+import org.junit.Assume
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -116,9 +118,9 @@
         underTest.setGroupExpanded(summary1, false)
 
         // Expanding again should throw.
-        assertThrows(IllegalArgumentException::class.java) {
-            underTest.setGroupExpanded(summary1, true)
-        }
+        // TODO(b/320238410): Remove this check when robolectric supports wtf assertions.
+        Assume.assumeFalse(Build.FINGERPRINT.contains("robolectric"))
+        assertLogsWtf { underTest.setGroupExpanded(summary1, true) }
     }
 
     @Test
diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
index a877853..0ecdcfc 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml
@@ -13,6 +13,16 @@
         android:scaleType="fitXY"
         android:visibility="gone" />
 
+    <TextView
+        android:id="@+id/logo_description"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="@integer/biometric_dialog_text_gravity"
+        android:singleLine="true"
+        android:marqueeRepeatLimit="1"
+        android:ellipsize="marquee"
+        android:visibility="gone"/>
+
     <ImageView
         android:id="@+id/background"
         android:layout_width="0dp"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
index 10f7113..e759074 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
@@ -28,6 +28,15 @@
         android:scaleType="fitXY"/>
 
     <TextView
+        android:id="@+id/logo_description"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="@integer/biometric_dialog_text_gravity"
+        android:singleLine="true"
+        android:marqueeRepeatLimit="1"
+        android:ellipsize="marquee"/>
+
+    <TextView
         android:id="@+id/title"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index c17c8dc..6133a51c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -40,6 +40,7 @@
         val contentView: PromptContentView? = info.contentView
         val logoRes: Int = info.logoRes
         val logoBitmap: Bitmap? = info.logoBitmap
+        val logoDescription: String? = info.logoDescription
         val negativeButtonText: String = info.negativeButtonText?.toString() ?: ""
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index efad21b..31aadf5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -95,6 +95,7 @@
             view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
 
         val logoView = view.requireViewById<ImageView>(R.id.logo)
+        val logoDescriptionView = view.requireViewById<TextView>(R.id.logo_description)
         val titleView = view.requireViewById<TextView>(R.id.title)
         val subtitleView = view.requireViewById<TextView>(R.id.subtitle)
         val descriptionView = view.requireViewById<TextView>(R.id.description)
@@ -104,6 +105,8 @@
         // set selected to enable marquee unless a screen reader is enabled
         logoView.isSelected =
             !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
+        logoDescriptionView.isSelected =
+            !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
         titleView.isSelected =
             !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
         subtitleView.isSelected =
@@ -165,6 +168,7 @@
             }
 
             logoView.setImageDrawable(viewModel.logo.first())
+            logoDescriptionView.text = viewModel.logoDescription.first()
             titleView.text = viewModel.title.first()
             subtitleView.text = viewModel.subtitle.first()
             descriptionView.text = viewModel.description.first()
@@ -197,6 +201,7 @@
                     viewsToHideWhenSmall =
                         listOf(
                             logoView,
+                            logoDescriptionView,
                             titleView,
                             subtitleView,
                             descriptionView,
@@ -205,6 +210,7 @@
                     viewsToFadeInOnSizeChange =
                         listOf(
                             logoView,
+                            logoDescriptionView,
                             titleView,
                             subtitleView,
                             descriptionView,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index ef5c37ea..788991d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics.ui.viewmodel
 
 import android.content.Context
+import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.graphics.Rect
 import android.graphics.drawable.BitmapDrawable
@@ -280,8 +281,9 @@
                     it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
                     else ->
                         try {
-                            context.packageManager.getApplicationIcon(it.opPackageName)
-                        } catch (e: PackageManager.NameNotFoundException) {
+                            val info = context.getApplicationInfo(it.opPackageName)
+                            context.packageManager.getApplicationIcon(info)
+                        } catch (e: Exception) {
                             Log.w(TAG, "Cannot find icon for package " + it.opPackageName, e)
                             null
                         }
@@ -289,6 +291,25 @@
             }
             .distinctUntilChanged()
 
+    /** Logo description for the prompt. */
+    val logoDescription: Flow<String> =
+        promptSelectorInteractor.prompt
+            .map {
+                when {
+                    !customBiometricPrompt() || it == null -> ""
+                    it.logoDescription != null -> it.logoDescription
+                    else ->
+                        try {
+                            val info = context.getApplicationInfo(it.opPackageName)
+                            context.packageManager.getApplicationLabel(info).toString()
+                        } catch (e: Exception) {
+                            Log.w(TAG, "Cannot find name for package " + it.opPackageName, e)
+                            ""
+                        }
+                }
+            }
+            .distinctUntilChanged()
+
     /** Title for the prompt. */
     val title: Flow<String> =
         promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged()
@@ -682,6 +703,12 @@
     }
 }
 
+private fun Context.getApplicationInfo(packageName: String): ApplicationInfo =
+    packageManager.getApplicationInfo(
+        packageName,
+        PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
+    )
+
 /** How the fingerprint sensor was started for the prompt. */
 enum class FingerprintStartMode {
     /** Fingerprint sensor has not started. */
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
index 6f7dcb1..297ad84 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.dreams.homecontrols
 
 import android.app.Activity
-import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
 import android.content.Intent
 import android.graphics.Rect
 import android.os.Binder
@@ -122,15 +122,14 @@
 
     /** Creates the task fragment */
     fun createTaskFragment() {
-        val taskBounds = Rect(activity.resources.configuration.windowConfiguration.bounds)
         val fragmentOptions =
             TaskFragmentCreationParams.Builder(
                     organizer.organizerToken,
                     fragmentToken,
                     activity.activityToken!!
                 )
-                .setInitialRelativeBounds(taskBounds)
-                .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
+                .setInitialRelativeBounds(Rect())
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
                 .build()
         organizer.applyTransaction(
             WindowContainerTransaction().createTaskFragment(fragmentOptions),
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 41ce3fd..83b2ee2 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -22,8 +22,10 @@
 import com.android.server.notification.Flags.crossAppPoliteNotifications
 import com.android.server.notification.Flags.politeNotifications
 import com.android.server.notification.Flags.vibrateWhileUnlocked
+import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
 import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
 import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT
+import com.android.systemui.Flags.communalHub
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.dagger.SysUISingleton
@@ -63,6 +65,9 @@
         ComposeLockscreen.token dependsOn KeyguardShadeMigrationNssl.token
         ComposeLockscreen.token dependsOn keyguardBottomAreaRefactor
         ComposeLockscreen.token dependsOn migrateClocksToBlueprint
+
+        // CommunalHub dependencies
+        communalHub dependsOn KeyguardShadeMigrationNssl.token
     }
 
     private inline val politeNotifications
@@ -75,4 +80,6 @@
         get() = FlagToken(FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
     private inline val migrateClocksToBlueprint
         get() = FlagToken(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, migrateClocksToBlueprint())
+    private inline val communalHub
+        get() = FlagToken(FLAG_COMMUNAL_HUB, communalHub())
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 9e7c70d..1b7a507 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -48,6 +48,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
+import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.clocks.ClockController
@@ -112,6 +113,10 @@
         }
 
         val burnInParams = MutableStateFlow(BurnInParameters())
+        val viewState =
+            ViewStateAccessor(
+                alpha = { view.alpha },
+            )
 
         val disposableHandle =
             view.repeatWhenAttached {
@@ -134,7 +139,7 @@
 
                     if (keyguardBottomAreaRefactor()) {
                         launch {
-                            viewModel.alpha.collect { alpha ->
+                            viewModel.alpha(viewState).collect { alpha ->
                                 view.alpha = alpha
                                 childViews[statusViewId]?.alpha = alpha
                             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index ec13228..83be651 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -60,18 +60,21 @@
     private val deviceEntryInteractor: DeviceEntryInteractor,
     private val dozeParameters: DozeParameters,
     private val keyguardInteractor: KeyguardInteractor,
-    communalInteractor: CommunalInteractor,
+    private val communalInteractor: CommunalInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
-    aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
-    lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
-    alternateBouncerToGoneTransitionViewModel: AlternateBouncerToGoneTransitionViewModel,
-    primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
-    lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
-    glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
-    screenOffAnimationController: ScreenOffAnimationController,
+    private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+    private val lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel,
+    private val alternateBouncerToGoneTransitionViewModel:
+        AlternateBouncerToGoneTransitionViewModel,
+    private val primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel,
+    private val lockscreenToGlanceableHubTransitionViewModel:
+        LockscreenToGlanceableHubTransitionViewModel,
+    private val glanceableHubToLockscreenTransitionViewModel:
+        GlanceableHubToLockscreenTransitionViewModel,
+    private val screenOffAnimationController: ScreenOffAnimationController,
     private val aodBurnInViewModel: AodBurnInViewModel,
-    aodAlphaViewModel: AodAlphaViewModel,
+    private val aodAlphaViewModel: AodAlphaViewModel,
 ) {
 
     val burnInLayerVisibility: Flow<Int> =
@@ -101,8 +104,8 @@
     val topClippingBounds: Flow<Int?> = keyguardInteractor.topClippingBounds
 
     /** An observable for the alpha level for the entire keyguard root view. */
-    val alpha: Flow<Float> =
-        combine(
+    fun alpha(viewState: ViewStateAccessor): Flow<Float> {
+        return combine(
                 communalInteractor.isIdleOnCommunal,
                 // The transitions are mutually exclusive, so they are safe to merge to get the last
                 // value emitted by any of them. Do not add flows that cannot make this guarantee.
@@ -110,7 +113,7 @@
                     aodAlphaViewModel.alpha,
                     lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
                     glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
-                    lockscreenToGoneTransitionViewModel.lockscreenAlpha,
+                    lockscreenToGoneTransitionViewModel.lockscreenAlpha(viewState),
                     primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
                     alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
                 )
@@ -125,6 +128,7 @@
                 }
             }
             .distinctUntilChanged()
+    }
 
     /** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */
     val lockscreenStateAlpha: Flow<Float> = aodToLockscreenTransitionViewModel.lockscreenAlpha
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
index d981650..15459f4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt
@@ -16,10 +16,12 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.util.MathUtils
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow.FlowBuilder
 import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -37,7 +39,7 @@
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
 
-    private val transitionAnimation =
+    private val transitionAnimation: FlowBuilder =
         animationFlow.setup(
             duration = FromLockscreenTransitionInteractor.TO_GONE_DURATION,
             from = KeyguardState.LOCKSCREEN,
@@ -52,7 +54,26 @@
             onCancel = { 1f },
         )
 
-    val lockscreenAlpha: Flow<Float> = shortcutsAlpha
+    fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+        var startAlpha: Float? = null
+        return transitionAnimation.sharedFlow(
+            duration = 200.milliseconds,
+            onStep = {
+                if (startAlpha == null) {
+                    startAlpha = viewState.alpha()
+                }
+                MathUtils.lerp(startAlpha!!, 0f, it)
+            },
+            onFinish = {
+                startAlpha = null
+                0f
+            },
+            onCancel = {
+                startAlpha = null
+                1f
+            },
+        )
+    }
 
     override val deviceEntryParentViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(0f)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ViewStateAccessor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ViewStateAccessor.kt
new file mode 100644
index 0000000..cb5db86
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/ViewStateAccessor.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+/** View-level state information to be shared between ui and viewmodel. */
+data class ViewStateAccessor(
+    val alpha: () -> Float = { 0f },
+    val translationY: () -> Int = { 0 },
+    val translationX: () -> Int = { 0 },
+)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index 40a9b9c..13d743f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -141,6 +141,7 @@
                 if (field != value) {
                     field = value
                     checkIfPollingNeeded()
+                    _data = _data.copy(listening = value)
                 }
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt
new file mode 100644
index 0000000..f7b45e5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.shared.model
+
+import kotlinx.coroutines.flow.StateFlow
+
+/** Defines interface for classes that provide access to scene state. */
+interface SceneDataSource {
+
+    /**
+     * The current scene, as seen by the real data source in the UI layer.
+     *
+     * During a transition between two scenes, the original scene will still be reflected in
+     * [currentScene] until a time when the UI layer decides to commit the change, which is when
+     * [currentScene] will have the value of the target/new scene.
+     */
+    val currentScene: StateFlow<SceneKey>
+
+    /**
+     * Asks for an asynchronous scene switch to [toScene], which will use the corresponding
+     * installed transition or the one specified by [transitionKey], if provided.
+     */
+    fun changeScene(
+        toScene: SceneKey,
+        transitionKey: TransitionKey? = null,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
new file mode 100644
index 0000000..a50830c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.shared.model
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.stateIn
+
+/**
+ * Delegates calls to a runtime-provided [SceneDataSource] or to a no-op implementation if a
+ * delegate isn't set.
+ */
+@SysUISingleton
+class SceneDataSourceDelegator
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    config: SceneContainerConfig,
+) : SceneDataSource {
+
+    private val noOpDelegate = NoOpSceneDataSource(config.initialSceneKey)
+    private val delegateMutable = MutableStateFlow<SceneDataSource>(noOpDelegate)
+
+    override val currentScene: StateFlow<SceneKey> =
+        delegateMutable
+            .flatMapLatest { delegate -> delegate.currentScene }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = config.initialSceneKey,
+            )
+
+    override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) {
+        delegateMutable.value.changeScene(
+            toScene = toScene,
+            transitionKey = transitionKey,
+        )
+    }
+
+    /**
+     * Binds the current, dependency injection provided [SceneDataSource] to the given object.
+     *
+     * In other words: once this is invoked, the state and functionality of the [SceneDataSource]
+     * will be served by the given [delegate].
+     *
+     * If `null` is passed in, the delegator will use a no-op implementation of [SceneDataSource].
+     *
+     * This removes any previously set delegate.
+     */
+    fun setDelegate(delegate: SceneDataSource?) {
+        delegateMutable.value = delegate ?: noOpDelegate
+    }
+
+    private class NoOpSceneDataSource(
+        initialSceneKey: SceneKey,
+    ) : SceneDataSource {
+        override val currentScene: StateFlow<SceneKey> =
+            MutableStateFlow(initialSceneKey).asStateFlow()
+        override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) = Unit
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKey.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKey.kt
new file mode 100644
index 0000000..87332ae
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/TransitionKey.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.scene.shared.model
+
+/**
+ * Key for a transition. This can be used to specify which transition spec should be used when
+ * starting the transition between two scenes.
+ */
+data class TransitionKey(
+    val debugName: String,
+    val identity: Any = Object(),
+)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/UserActionDistance.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/UserActionDistance.kt
new file mode 100644
index 0000000..b93f837
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/UserActionDistance.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.scene.shared.model
+
+interface UserActionDistance {
+
+    /**
+     * Return the **absolute** distance of the user action (in pixels) given the size of the scene
+     * we are animating from and the orientation.
+     */
+    fun absoluteDistance(fromSceneWidth: Int, fromSceneHeight: Int, isHorizontal: Boolean): Float
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/UserActionResult.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/UserActionResult.kt
new file mode 100644
index 0000000..e1b96e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/UserActionResult.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.scene.shared.model
+
+data class UserActionResult(
+
+    /** The scene we should be transitioning due to the [UserAction]. */
+    val toScene: SceneKey,
+
+    /**
+     * The distance the action takes to animate from 0% to 100%.
+     *
+     * If `null`, a default distance will be used depending on the [UserAction] performed.
+     */
+    val distance: UserActionDistance? = null,
+
+    /**
+     * The key of the transition that should be used, if a specific one should be used.
+     *
+     * If `null`, the transition used will be the corresponding transition from the collection
+     * passed into the UI layer.
+     */
+    val transitionKey: TransitionKey? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
index 90abec1..80c3551 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilder.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar;
 
+import static android.app.Flags.lifetimeExtensionRefactor;
+
 import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.RemoteInputHistoryItem;
@@ -29,6 +31,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.stream.Stream;
 
@@ -68,7 +71,7 @@
     @NonNull
     public StatusBarNotification rebuildForCanceledSmartReplies(
             NotificationEntry entry) {
-        return rebuildWithRemoteInputInserted(entry, null /* remoteInputTest */,
+        return rebuildWithRemoteInputInserted(entry, null /* remoteInputText */,
                 false /* showSpinner */, null /* mimeType */, null /* uri */);
     }
 
@@ -97,22 +100,50 @@
     StatusBarNotification rebuildWithRemoteInputInserted(NotificationEntry entry,
             CharSequence remoteInputText, boolean showSpinner, String mimeType, Uri uri) {
         StatusBarNotification sbn = entry.getSbn();
-
         Notification.Builder b = Notification.Builder
                 .recoverBuilder(mContext, sbn.getNotification().clone());
-        if (remoteInputText != null || uri != null) {
-            RemoteInputHistoryItem newItem = uri != null
-                    ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText)
-                    : new RemoteInputHistoryItem(remoteInputText);
+
+        if (lifetimeExtensionRefactor()) {
+            if (entry.remoteInputs == null) {
+                entry.remoteInputs = new ArrayList<RemoteInputHistoryItem>();
+            }
+
+            // Append new remote input information to remoteInputs list
+            if (remoteInputText != null || uri != null) {
+                RemoteInputHistoryItem newItem = uri != null
+                        ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText)
+                        : new RemoteInputHistoryItem(remoteInputText);
+                // The list is latest-first, so new elements should be added as the first element.
+                entry.remoteInputs.add(0, newItem);
+            }
+
+            // Read the whole remoteInputs list from the entry, then append all of those to the sbn.
             Parcelable[] oldHistoryItems = sbn.getNotification().extras
                     .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+
             RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null
                     ? Stream.concat(
-                    Stream.of(newItem),
-                    Arrays.stream(oldHistoryItems).map(p -> (RemoteInputHistoryItem) p))
+                            entry.remoteInputs.stream(),
+                            Arrays.stream(oldHistoryItems).map(p -> (RemoteInputHistoryItem) p))
                     .toArray(RemoteInputHistoryItem[]::new)
-                    : new RemoteInputHistoryItem[] { newItem };
+                    : entry.remoteInputs.toArray(RemoteInputHistoryItem[]::new);
             b.setRemoteInputHistory(newHistoryItems);
+
+        } else {
+            if (remoteInputText != null || uri != null) {
+                RemoteInputHistoryItem newItem = uri != null
+                        ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText)
+                        : new RemoteInputHistoryItem(remoteInputText);
+                Parcelable[] oldHistoryItems = sbn.getNotification().extras
+                        .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
+                RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null
+                        ? Stream.concat(
+                                Stream.of(newItem),
+                                Arrays.stream(oldHistoryItems).map(p -> (RemoteInputHistoryItem) p))
+                        .toArray(RemoteInputHistoryItem[]::new)
+                        : new RemoteInputHistoryItem[]{newItem};
+                b.setRemoteInputHistory(newHistoryItems);
+            }
         }
         b.setShowRemoteInputSpinner(showSpinner);
         b.setHideSmartReplies(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index cdacb10..8678f0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -40,6 +40,7 @@
 import android.app.NotificationManager.Policy;
 import android.app.Person;
 import android.app.RemoteInput;
+import android.app.RemoteInputHistoryItem;
 import android.content.Context;
 import android.content.pm.ShortcutInfo;
 import android.net.Uri;
@@ -127,6 +128,7 @@
     public int targetSdk;
     private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
     public CharSequence remoteInputText;
+    public List<RemoteInputHistoryItem> remoteInputs = null;
     public String remoteInputMimeType;
     public Uri remoteInputUri;
     public ContentInfo remoteInputAttachment;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
index 918bf08..28fff15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.collection.coordinator
 
+import android.app.Flags.lifetimeExtensionRefactor
+import android.app.Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY
 import android.os.Handler
 import android.service.notification.NotificationListenerService.REASON_CANCEL
 import android.service.notification.NotificationListenerService.REASON_CLICK
@@ -88,11 +90,21 @@
 
     override fun attach(pipeline: NotifPipeline) {
         mNotificationRemoteInputManager.setRemoteInputListener(this)
-        mRemoteInputLifetimeExtenders.forEach { pipeline.addNotificationLifetimeExtender(it) }
+        if (lifetimeExtensionRefactor()) {
+            pipeline.addNotificationLifetimeExtender(mRemoteInputActiveExtender)
+        } else {
+            mRemoteInputLifetimeExtenders.forEach {
+                pipeline.addNotificationLifetimeExtender(it)
+            }
+        }
         mNotifUpdater = pipeline.getInternalNotifUpdater(TAG)
         pipeline.addCollectionListener(mCollectionListener)
     }
 
+    /*
+     * Listener that updates the appearance of the notification if it has been lifetime extended
+     * by a a direct reply or a smart reply, and cancelled.
+     */
     val mCollectionListener = object : NotifCollectionListener {
         override fun onEntryUpdated(entry: NotificationEntry, fromSystem: Boolean) {
             if (DEBUG) {
@@ -100,9 +112,32 @@
                         " fromSystem=$fromSystem)")
             }
             if (fromSystem) {
-                // Mark smart replies as sent whenever a notification is updated by the app,
-                // otherwise the smart replies are never marked as sent.
-                mSmartReplyController.stopSending(entry)
+                if (lifetimeExtensionRefactor()) {
+                    if ((entry.getSbn().getNotification().flags
+                                    and FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
+                        if (mNotificationRemoteInputManager.shouldKeepForRemoteInputHistory(
+                                        entry)) {
+                            val newSbn = mRebuilder.rebuildForRemoteInputReply(entry)
+                            entry.onRemoteInputInserted()
+                            mNotifUpdater.onInternalNotificationUpdate(newSbn,
+                                    "Extending lifetime of notification with remote input")
+                        } else if (mNotificationRemoteInputManager.shouldKeepForSmartReplyHistory(
+                                        entry)) {
+                            val newSbn = mRebuilder.rebuildForCanceledSmartReplies(entry)
+                            mSmartReplyController.stopSending(entry)
+                            mNotifUpdater.onInternalNotificationUpdate(newSbn,
+                                    "Extending lifetime of notification with smart reply")
+                        }
+                    } else {
+                        // Notifications updated without FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY
+                        // should have their remote inputs list cleared.
+                        entry.remoteInputs = null
+                    }
+                } else {
+                    // Mark smart replies as sent whenever a notification is updated by the app,
+                    // otherwise the smart replies are never marked as sent.
+                    mSmartReplyController.stopSending(entry)
+                }
             }
         }
 
@@ -130,8 +165,10 @@
         // NOTE: This is some trickery! By removing the lifetime extensions when we know they should
         // be immediately re-upped, we ensure that the side-effects of the lifetime extenders get to
         // fire again, thus ensuring that we add subsequent replies to the notification.
-        mRemoteInputHistoryExtender.endLifetimeExtension(entry.key)
-        mSmartReplyHistoryExtender.endLifetimeExtension(entry.key)
+        if (!lifetimeExtensionRefactor()) {
+            mRemoteInputHistoryExtender.endLifetimeExtension(entry.key)
+            mSmartReplyHistoryExtender.endLifetimeExtension(entry.key)
+        }
 
         // If we're extending for remote input being active, then from the apps point of
         // view it is already canceled, so we'll need to cancel it on the apps behalf
@@ -160,15 +197,19 @@
     }
 
     override fun isNotificationKeptForRemoteInputHistory(key: String) =
+        if (!lifetimeExtensionRefactor()) {
             mRemoteInputHistoryExtender.isExtending(key) ||
                     mSmartReplyHistoryExtender.isExtending(key)
+        } else false
 
     override fun releaseNotificationIfKeptForRemoteInputHistory(entry: NotificationEntry) {
         if (DEBUG) Log.d(TAG, "releaseNotificationIfKeptForRemoteInputHistory(entry=${entry.key})")
-        mRemoteInputHistoryExtender.endLifetimeExtensionAfterDelay(entry.key,
-                REMOTE_INPUT_EXTENDER_RELEASE_DELAY)
-        mSmartReplyHistoryExtender.endLifetimeExtensionAfterDelay(entry.key,
-                REMOTE_INPUT_EXTENDER_RELEASE_DELAY)
+        if (!lifetimeExtensionRefactor()) {
+            mRemoteInputHistoryExtender.endLifetimeExtensionAfterDelay(entry.key,
+                    REMOTE_INPUT_EXTENDER_RELEASE_DELAY)
+            mSmartReplyHistoryExtender.endLifetimeExtensionAfterDelay(entry.key,
+                    REMOTE_INPUT_EXTENDER_RELEASE_DELAY)
+        }
         mRemoteInputActiveExtender.endLifetimeExtensionAfterDelay(entry.key,
                 REMOTE_INPUT_EXTENDER_RELEASE_DELAY)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
index 3cdb2cd..d1aff80 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification.collection.render;
 
+import android.util.Log;
+
 import androidx.annotation.NonNull;
 
 import com.android.systemui.Dumpable;
@@ -40,6 +42,8 @@
  */
 @SysUISingleton
 public class GroupExpansionManagerImpl implements GroupExpansionManager, Dumpable {
+    private static final String TAG = "GroupExpansionaManagerImpl";
+
     private final DumpManager mDumpManager;
     private final GroupMembershipManager mGroupMembershipManager;
     private final Set<OnGroupExpansionChangeListener> mOnGroupChangeListeners = new HashSet<>();
@@ -100,7 +104,7 @@
         NotificationEntry groupSummary = mGroupMembershipManager.getGroupSummary(entry);
         if (entry.getParent() == null) {
             if (expanded) {
-                throw new IllegalArgumentException("Cannot expand group that is not attached");
+                Log.wtf(TAG, "Cannot expand group that is not attached");
             } else {
                 // The entry is no longer attached, but we still want to make sure we don't have
                 // a stale expansion state.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index de3a626..c8d6abe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -311,12 +311,13 @@
                                 boundViewsByNotifKey[it.notifKey]?.first
                             }
                         val childCount = view.childCount
+                        val toRemove = mutableListOf<View>()
                         for (i in 0 until childCount) {
                             val actual = view.getChildAt(i)
                             val expected = expectedChildren.getOrNull(i)
                             if (expected == null) {
                                 Log.wtf(TAG, "[$logTag] Unexpected child $actual")
-                                view.removeView(actual)
+                                toRemove.add(actual)
                                 continue
                             }
                             if (actual === expected) {
@@ -325,6 +326,9 @@
                             view.removeView(expected)
                             view.addView(expected, i)
                         }
+                        for (child in toRemove) {
+                            view.removeView(child)
+                        }
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 47daf49..830b8c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1335,6 +1335,10 @@
         }
     }
 
+    public float getAlpha() {
+        return mView.getAlpha();
+    }
+
     public void setSuppressChildrenMeasureAndLayout(boolean suppressLayout) {
         mView.suppressChildrenMeasureAndLayout(suppressLayout);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index 97db9b6..daea8af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -24,6 +24,7 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
@@ -75,6 +76,10 @@
             }
 
         val burnInParams = MutableStateFlow(BurnInParameters())
+        val viewState =
+            ViewStateAccessor(
+                alpha = { controller.getAlpha() },
+            )
 
         /*
          * For animation sensitive coroutines, immediately run just like applicationScope does
@@ -141,7 +146,7 @@
 
                     if (!sceneContainerFlags.isEnabled()) {
                         launch {
-                            viewModel.expansionAlpha.collect {
+                            viewModel.expansionAlpha(viewState).collect {
                                 controller.setMaxAlphaForExpansion(it)
                             }
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index e0c2c3b..ff00cb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -50,6 +50,7 @@
 import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
@@ -67,7 +68,6 @@
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
@@ -100,21 +100,35 @@
         setOf(AOD, LOCKSCREEN, DOZING, ALTERNATE_BOUNCER, PRIMARY_BOUNCER)
 
     private val edgeToAlphaViewModel =
-        mapOf<Edge?, Flow<Float>>(
+        mapOf<Edge?, (ViewStateAccessor) -> Flow<Float>>(
             Edge(from = LOCKSCREEN, to = DREAMING) to
-                lockscreenToDreamingTransitionViewModel.lockscreenAlpha,
+                { _: ViewStateAccessor ->
+                    lockscreenToDreamingTransitionViewModel.lockscreenAlpha
+                },
             Edge(from = LOCKSCREEN, to = GONE) to
-                lockscreenToGoneTransitionViewModel.lockscreenAlpha,
+                { viewState: ViewStateAccessor ->
+                    lockscreenToGoneTransitionViewModel.lockscreenAlpha(viewState)
+                },
             Edge(from = ALTERNATE_BOUNCER, to = GONE) to
-                alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
+                { _: ViewStateAccessor ->
+                    alternateBouncerToGoneTransitionViewModel.lockscreenAlpha
+                },
             Edge(from = PRIMARY_BOUNCER, to = GONE) to
-                primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
+                { _: ViewStateAccessor ->
+                    primaryBouncerToGoneTransitionViewModel.lockscreenAlpha
+                },
             Edge(from = DREAMING, to = LOCKSCREEN) to
-                dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
+                { _: ViewStateAccessor ->
+                    dreamingToLockscreenTransitionViewModel.lockscreenAlpha
+                },
             Edge(from = LOCKSCREEN, to = OCCLUDED) to
-                lockscreenToOccludedTransitionViewModel.lockscreenAlpha,
+                { _: ViewStateAccessor ->
+                    lockscreenToOccludedTransitionViewModel.lockscreenAlpha
+                },
             Edge(from = OCCLUDED, to = LOCKSCREEN) to
-                occludedToLockscreenTransitionViewModel.lockscreenAlpha,
+                { _: ViewStateAccessor ->
+                    occludedToLockscreenTransitionViewModel.lockscreenAlpha
+                },
         )
 
     private val lockscreenTransitionInProgress: Flow<Edge?> =
@@ -279,46 +293,63 @@
                 initialValue = NotificationContainerBounds(),
             )
 
-    /** As QS is expanding, fade out notifications unless in splitshade */
-    private val alphaForQsExpansion: Flow<Float> =
-        interactor.configurationBasedDimensions.flatMapLatest {
-            if (it.useSplitShade) {
-                flowOf(1f)
-            } else {
-                shadeInteractor.qsExpansion.map { 1f - it }
+    /**
+     * Ensure view is visible when the shade/qs are expanded. Also, as QS is expanding, fade out
+     * notifications unless in splitshade.
+     */
+    private val alphaForShadeAndQsExpansion: Flow<Float> =
+        interactor.configurationBasedDimensions
+            .flatMapLatest { configurationBasedDimensions ->
+                combine(
+                    shadeInteractor.shadeExpansion,
+                    shadeInteractor.qsExpansion,
+                ) { shadeExpansion, qsExpansion ->
+                    if (shadeExpansion > 0f || qsExpansion > 0f) {
+                        if (configurationBasedDimensions.useSplitShade) {
+                            1f
+                        } else {
+                            // Fade as QS shade expands
+                            1f - qsExpansion
+                        }
+                    } else {
+                        // Not visible unless the shade/qs is visible
+                        0f
+                    }
+                }
             }
-        }
+            .distinctUntilChanged()
 
-    val expansionAlpha: Flow<Float> =
+    fun expansionAlpha(viewState: ViewStateAccessor): Flow<Float> {
         // Due to issues with the legacy shade, some shade expansion events are sent incorrectly,
         // such as when the shade resets. This can happen while the transition to/from LOCKSCREEN
         // is running. Therefore use a series of flatmaps to prevent unwanted interruptions while
         // those transitions are in progress. Without this, the alpha value will produce a visible
         // flicker.
-        lockscreenTransitionInProgress
+        return lockscreenTransitionInProgress
             .flatMapLatest { edge ->
-                edgeToAlphaViewModel.getOrElse(
+                edgeToAlphaViewModel.getOrDefault(
                     edge,
-                    {
+                    { _: ViewStateAccessor ->
                         isOnLockscreenWithoutShade.flatMapLatest { isOnLockscreenWithoutShade ->
                             combineTransform(
                                 keyguardInteractor.keyguardAlpha,
                                 shadeCollpaseFadeIn,
-                                alphaForQsExpansion,
-                            ) { alpha, shadeCollpaseFadeIn, alphaForQsExpansion ->
+                                alphaForShadeAndQsExpansion,
+                            ) { alpha, shadeCollpaseFadeIn, alphaForShadeAndQsExpansion ->
                                 if (isOnLockscreenWithoutShade) {
                                     if (!shadeCollpaseFadeIn) {
                                         emit(alpha)
                                     }
                                 } else {
-                                    emit(alphaForQsExpansion)
+                                    emit(alphaForShadeAndQsExpansion)
                                 }
                             }
                         }
                     }
-                )
+                )(viewState)
             }
             .distinctUntilChanged()
+    }
 
     /**
      * Returns a flow of the expected alpha while running a LOCKSCREEN<->GLANCEABLE_HUB transition
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index ca3e3c6..db55da7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -43,6 +43,7 @@
  */
 public class KeyguardClockPositionAlgorithm {
     private static final String TAG = "KeyguardClockPositionAlgorithm";
+    private static final boolean DEBUG = false;
 
     /**
      * Margin between the bottom of the status view and the notification shade.
@@ -318,24 +319,26 @@
         }
 
         float fullyDarkBurnInOffset = burnInPreventionOffsetY(burnInPreventionOffsetY);
-        float clockYDark = clockY
-                + fullyDarkBurnInOffset
-                + shift;
+        float clockYDark = clockY + fullyDarkBurnInOffset + shift;
         mCurrentBurnInOffsetY = MathUtils.lerp(0, fullyDarkBurnInOffset, darkAmount);
-        final String inputs = "panelExpansion: " + panelExpansion + " darkAmount: " + darkAmount;
-        final String outputs = "clockY: " + clockY
-                + " burnInPreventionOffsetY: " + burnInPreventionOffsetY
-                + " fullyDarkBurnInOffset: " + fullyDarkBurnInOffset
-                + " shift: " + shift
-                + " mOverStretchAmount: " + mOverStretchAmount
-                + " mCurrentBurnInOffsetY: " + mCurrentBurnInOffsetY;
-        mLogger.i(msg -> {
-            return msg.getStr1() + " -> " + msg.getStr2();
-        }, msg -> {
-            msg.setStr1(inputs);
-            msg.setStr2(outputs);
-            return kotlin.Unit.INSTANCE;
-        });
+
+        if (DEBUG) {
+            final float finalShift = shift;
+            final float finalBurnInPreventionOffsetY = burnInPreventionOffsetY;
+            mLogger.i(msg -> {
+                final String inputs = "panelExpansion: " + panelExpansion
+                        + " darkAmount: " + darkAmount;
+                final String outputs = "clockY: " + clockY
+                        + " burnInPreventionOffsetY: " + finalBurnInPreventionOffsetY
+                        + " fullyDarkBurnInOffset: " + fullyDarkBurnInOffset
+                        + " shift: " + finalShift
+                        + " mOverStretchAmount: " + mOverStretchAmount
+                        + " mCurrentBurnInOffsetY: " + mCurrentBurnInOffsetY;
+                return inputs + " -> " + outputs;
+            }, msg -> {
+                return kotlin.Unit.INSTANCE;
+            });
+        }
         return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 45bdae8..8ac3b4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -132,9 +132,9 @@
         if (updateDisplayParameters()) {
             updateLayoutForCutout();
             requestLayout();
-            if (truncatedStatusBarIconsFix()) {
-                updateWindowHeight();
-            }
+        }
+        if (truncatedStatusBarIconsFix()) {
+            updateWindowHeight();
         }
     }
 
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 fc2f6e9..c089092 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -35,6 +35,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.settingslib.bluetooth.LocalBluetoothProfile;
 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.systemui.Flags;
 import com.android.systemui.bluetooth.BluetoothLogger;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -240,9 +241,21 @@
     @WorkerThread
     @Override
     public String getConnectedDeviceName() {
-        synchronized (mConnectedDevices) {
-            if (mConnectedDevices.size() == 1) {
-                return mConnectedDevices.get(0).getName();
+        if (Flags.getConnectedDeviceNameUnsynchronized()) {
+            CachedBluetoothDevice connectedDevice = null;
+            // Calling the getName() API for CachedBluetoothDevice outside the synchronized block
+            // so that the main thread is not blocked.
+            synchronized (mConnectedDevices) {
+                if (mConnectedDevices.size() == 1) {
+                    connectedDevice = mConnectedDevices.get(0);
+                }
+            }
+            return connectedDevice != null ? connectedDevice.getName() : null;
+        } else {
+            synchronized (mConnectedDevices) {
+                if (mConnectedDevices.size() == 1) {
+                    return mConnectedDevices.get(0).getName();
+                }
             }
         }
         return null;
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 9f4a906..f397627 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -444,7 +444,8 @@
                     UserHandle.of(userId))) {
                 boolean hasCACerts = !(conn.getService().getUserCaAliases().getList().isEmpty());
                 idWithCert = new Pair<Integer, Boolean>(userId, hasCACerts);
-            } catch (RemoteException | InterruptedException | AssertionError e) {
+            } catch (RemoteException | InterruptedException | AssertionError
+                     | IllegalStateException e) {
                 Log.i(TAG, "failed to get CA certs", e);
                 idWithCert = new Pair<Integer, Boolean>(userId, null);
             } finally {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS
new file mode 100644
index 0000000..43450c4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS
@@ -0,0 +1,5 @@
+# Android TV Core Framework
+rgl@google.com
+valiiftime@google.com
+galinap@google.com
+robhor@google.com
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt
index 1c17fc3..bb4a2c4 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/WalletContextualLocationsService.kt
@@ -77,17 +77,17 @@
         controller.setSuggestionCardIds(storeLocations.toSet())
     }
 
-    private val binder: IWalletContextualLocationsService.Stub
-    = object : IWalletContextualLocationsService.Stub() {
-        override fun addWalletCardsUpdatedListener(listener: IWalletCardsUpdatedListener) {
-            addWalletCardsUpdatedListenerInternal(listener)
+    private val binder: IWalletContextualLocationsService.Stub =
+        object : IWalletContextualLocationsService.Stub() {
+            override fun addWalletCardsUpdatedListener(listener: IWalletCardsUpdatedListener) {
+                addWalletCardsUpdatedListenerInternal(listener)
+            }
+            override fun onWalletContextualLocationsStateUpdated(storeLocations: List<String>) {
+                onWalletContextualLocationsStateUpdatedInternal(storeLocations)
+            }
         }
-        override fun onWalletContextualLocationsStateUpdated(storeLocations: List<String>) {
-            onWalletContextualLocationsStateUpdatedInternal(storeLocations)
-        }
-    }
 
     companion object {
         private const val TAG = "WalletContextualLocationsService"
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt b/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt
index 0fe2283..f23fbee 100644
--- a/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt
+++ b/packages/SystemUI/tests/src/android/animation/AnimatorTestRuleIsolationTest.kt
@@ -34,7 +34,7 @@
 @RunWithLooper
 class AnimatorTestRuleIsolationTest : SysuiTestCase() {
 
-    @get:Rule val animatorTestRule = AnimatorTestRule()
+    @get:Rule val animatorTestRule = AnimatorTestRule(this)
 
     @Test
     fun testA() {
diff --git a/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt b/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt
index cc7f7e4..fd5f157 100644
--- a/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt
+++ b/packages/SystemUI/tests/src/android/animation/AnimatorTestRulePrecisionTest.kt
@@ -31,7 +31,7 @@
 @RunWithLooper
 class AnimatorTestRulePrecisionTest : SysuiTestCase() {
 
-    @get:Rule val animatorTestRule = AnimatorTestRule()
+    @get:Rule val animatorTestRule = AnimatorTestRule(this)
 
     var value1: Float = -1f
     var value2: Float = -1f
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index e6637e6..cd19259 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -22,6 +22,7 @@
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -259,6 +260,7 @@
     @Test
     fun keyguardCallback_visibilityChanged_clockDozeCalled() =
         runBlocking(IMMEDIATE) {
+            mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL)
             val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
             verify(keyguardUpdateMonitor).registerCallback(capture(captor))
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index fad8552..e893eb1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -56,7 +56,7 @@
 public class KeyguardStatusViewControllerTest extends KeyguardStatusViewControllerBaseTest {
 
     @Rule
-    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this);
 
     @Test
     public void dozeTimeTick_updatesSlice() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index ba27fcd..dd428f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -48,7 +48,7 @@
 public class ExpandHelperTest extends SysuiTestCase {
 
     @Rule
-    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this);
 
     private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
     private ExpandableNotificationRow mRow;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index e006d59..64936862 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -82,7 +82,7 @@
 public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
 
     @Rule
-    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this);
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
     private static final float DEFAULT_SCALE = 4.0f;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 2225ad6..f1b0c18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -129,7 +129,8 @@
 public class WindowMagnificationControllerTest extends SysuiTestCase {
 
     @Rule
-    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+    // NOTE: pass 'null' to allow this test advances time on the main thread.
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(null);
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
index a35a509..08b49e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerWindowlessMagnifierTest.java
@@ -132,7 +132,7 @@
 public class WindowMagnificationControllerWindowlessMagnifierTest extends SysuiTestCase {
 
     @Rule
-    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this);
     @Rule
     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
index 2b51ac5..f07932c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
@@ -19,7 +19,6 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import androidx.core.animation.doOnEnd
-import androidx.test.filters.FlakyTest
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.util.doOnEnd
@@ -31,10 +30,9 @@
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 @RunWithLooper
-@FlakyTest(bugId = 302149604)
 class AnimatorTestRuleOrderTest : SysuiTestCase() {
 
-    @get:Rule val animatorTestRule = AnimatorTestRule()
+    @get:Rule val animatorTestRule = AnimatorTestRule(this)
 
     var value1: Float = -1f
     var value2: Float = -1f
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
index a46167a42..8fab233 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -26,6 +26,7 @@
     @Test
     fun biometricRequestFromPromptInfo() {
         val logoRes = R.drawable.ic_cake
+        val logoDescription = "test cake"
         val title = "what"
         val subtitle = "a"
         val description = "request"
@@ -41,6 +42,7 @@
             BiometricPromptRequest.Biometric(
                 promptInfo(
                     logoRes = logoRes,
+                    logoDescription = logoDescription,
                     title = title,
                     subtitle = subtitle,
                     description = description,
@@ -53,6 +55,7 @@
             )
 
         assertThat(request.logoRes).isEqualTo(logoRes)
+        assertThat(request.logoDescription).isEqualTo(logoDescription)
         assertThat(request.title).isEqualTo(title)
         assertThat(request.subtitle).isEqualTo(subtitle)
         assertThat(request.description).isEqualTo(description)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 2e94d38..ff68fe3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.biometrics.ui.viewmodel
 
+import android.content.pm.ApplicationInfo
 import android.content.pm.PackageManager
 import android.content.res.Configuration
 import android.graphics.Bitmap
@@ -74,6 +75,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.junit.MockitoJUnit
 
@@ -95,6 +98,8 @@
     @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var udfpsUtils: UdfpsUtils
     @Mock private lateinit var packageManager: PackageManager
+    @Mock private lateinit var applicationInfoWithIcon: ApplicationInfo
+    @Mock private lateinit var applicationInfoNoIcon: ApplicationInfo
 
     private val fakeExecutor = FakeExecutor(FakeSystemClock())
     private val testScope = TestScope()
@@ -102,6 +107,8 @@
     private val logoResFromApp = R.drawable.ic_cake
     private val logoFromApp = context.getDrawable(logoResFromApp)
     private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565)
+    private val defaultLogoDescription = "Test Android App"
+    private val logoDescriptionFromApp = "Test Cake App"
 
     private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
     private lateinit var promptRepository: FakePromptRepository
@@ -166,7 +173,14 @@
         iconViewModel = viewModel.iconViewModel
 
         // Set up default logo icon and app customized icon
-        whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
+        whenever(packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME_NO_ICON), anyInt()))
+            .thenReturn(applicationInfoNoIcon)
+        whenever(packageManager.getApplicationInfo(eq(OP_PACKAGE_NAME), anyInt()))
+            .thenReturn(applicationInfoWithIcon)
+        whenever(packageManager.getApplicationIcon(applicationInfoWithIcon))
+            .thenReturn(defaultLogoIcon)
+        whenever(packageManager.getApplicationLabel(applicationInfoWithIcon))
+            .thenReturn(defaultLogoDescription)
         context.setMockPackageManager(packageManager)
         val resources = context.getOrCreateTestableResources()
         resources.addOverride(logoResFromApp, logoFromApp)
@@ -1277,6 +1291,29 @@
             assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
         }
 
+    @Test
+    fun logoDescriptionIsEmptyIfPackageNameNotFound() =
+        runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) {
+            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+            val logoDescription by collectLastValue(viewModel.logoDescription)
+            assertThat(logoDescription).isEqualTo("")
+        }
+
+    @Test
+    fun defaultLogoDescriptionIfNoLogoDescriptionSet() = runGenericTest {
+        mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+        val logoDescription by collectLastValue(viewModel.logoDescription)
+        assertThat(logoDescription).isEqualTo(defaultLogoDescription)
+    }
+
+    @Test
+    fun logoDescriptionSetByApp() =
+        runGenericTest(logoDescription = logoDescriptionFromApp) {
+            mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+            val logoDescription by collectLastValue(viewModel.logoDescription)
+            assertThat(logoDescription).isEqualTo(logoDescriptionFromApp)
+        }
+
     /** Asserts that the selected buttons are visible now. */
     private suspend fun TestScope.assertButtonsVisible(
         tryAgain: Boolean = false,
@@ -1300,6 +1337,7 @@
         contentView: PromptContentView? = null,
         logoRes: Int = -1,
         logoBitmap: Bitmap? = null,
+        logoDescription: String? = null,
         packageName: String = OP_PACKAGE_NAME,
         block: suspend TestScope.() -> Unit,
     ) {
@@ -1312,6 +1350,7 @@
             contentViewFromApp = contentView,
             logoResFromApp = logoRes,
             logoBitmapFromApp = logoBitmap,
+            logoDescriptionFromApp = logoDescription,
             packageName = packageName,
         )
 
@@ -1492,12 +1531,14 @@
     contentViewFromApp: PromptContentView? = null,
     logoResFromApp: Int = -1,
     logoBitmapFromApp: Bitmap? = null,
+    logoDescriptionFromApp: String? = null,
     packageName: String = OP_PACKAGE_NAME,
 ) {
     val info =
         PromptInfo().apply {
             logoRes = logoResFromApp
             logoBitmap = logoBitmapFromApp
+            logoDescription = logoDescriptionFromApp
             title = "t"
             subtitle = "s"
             description = descriptionFromApp
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
index 5b29a86..7787a7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
@@ -43,7 +43,7 @@
 @RunWithLooper(setAsMainLooper = true)
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 class KeyguardSurfaceBehindParamsApplierTest : SysuiTestCase() {
-    @get:Rule val animatorTestRule = AnimatorTestRule()
+    @get:Rule val animatorTestRule = AnimatorTestRule(this)
 
     private lateinit var underTest: KeyguardSurfaceBehindParamsApplier
     private lateinit var executor: FakeExecutor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt
index 8b05a54..e7aaddd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModelTest.kt
@@ -19,6 +19,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -56,6 +57,41 @@
             deviceEntryParentViewAlpha.forEach { assertThat(it).isEqualTo(0f) }
         }
 
+    @Test
+    fun lockscreenAlphaStartsFromViewStateAccessorAlpha() =
+        testScope.runTest {
+            val viewState = ViewStateAccessor(alpha = { 0.5f })
+            val alpha by collectLastValue(underTest.lockscreenAlpha(viewState))
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+
+            repository.sendTransitionStep(step(0f))
+            assertThat(alpha).isEqualTo(0.5f)
+
+            repository.sendTransitionStep(step(0.25f))
+            assertThat(alpha).isEqualTo(0.25f)
+
+            repository.sendTransitionStep(step(.5f))
+            assertThat(alpha).isEqualTo(0f)
+        }
+
+    @Test
+    fun lockscreenAlphaWithNoViewStateAccessorValue() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.lockscreenAlpha(ViewStateAccessor()))
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+
+            repository.sendTransitionStep(step(0f))
+            assertThat(alpha).isEqualTo(0f)
+
+            repository.sendTransitionStep(step(0.25f))
+            assertThat(alpha).isEqualTo(0f)
+
+            repository.sendTransitionStep(step(0.5f))
+            assertThat(alpha).isEqualTo(0f)
+        }
+
     private fun step(
         value: Float,
         state: TransitionState = TransitionState.RUNNING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
index 100e579..4ec29ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarObserverTest.kt
@@ -164,6 +164,18 @@
     }
 
     @Test
+    fun seekbarNotListeningNotScrubbingPlaying() {
+        // WHEN playing
+        val isPlaying = true
+        val isScrubbing = false
+        val data =
+            SeekBarViewModel.Progress(true, true, isPlaying, isScrubbing, 3000, 120000, false)
+        observer.onChanged(data)
+        // THEN progress drawable is not animating
+        verify(mockSquigglyProgress).animate = false
+    }
+
+    @Test
     fun seekBarPlayingScrubbing() {
         // WHEN playing & scrubbing
         val isPlaying = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
index bbae0c9..ae2a9ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt
@@ -40,7 +40,7 @@
 @SmallTest
 class QSIconViewImplTest_311121830 : SysuiTestCase() {
 
-    @get:Rule val animatorRule = AnimatorTestRule()
+    @get:Rule val animatorRule = AnimatorTestRule(this)
 
     @Test
     fun alwaysLastIcon() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 0c4bf81..ab5e51c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -207,6 +207,7 @@
     @Test
     fun testDragDownHelperCalledWhenDraggingDown() =
         testScope.runTest {
+            mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL)
             whenever(dragDownHelper.isDraggingDown).thenReturn(true)
             val now = SystemClock.elapsedRealtime()
             val ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0f, 0f, 0 /* meta */)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index c226121..4cc1234 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -26,6 +26,7 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.fragments.FragmentHostManager
@@ -400,6 +401,7 @@
 
     @Test
     fun testSplitShadeLayout_isAlignedToGuideline() {
+        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL)
         enableSplitShade()
         underTest.updateResources()
         assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline)
@@ -409,6 +411,7 @@
 
     @Test
     fun testSinglePaneLayout_childrenHaveEqualMargins() {
+        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL)
         disableSplitShade()
         underTest.updateResources()
         val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin
@@ -425,6 +428,7 @@
 
     @Test
     fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() {
+        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL)
         enableSplitShade()
         underTest.updateResources()
         assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0)
@@ -443,6 +447,7 @@
     @Test
     fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderHeightResource() {
         mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL)
         setLargeScreen()
         val largeScreenHeaderResourceHeight = 100
         val largeScreenHeaderHelperHeight = 200
@@ -465,6 +470,7 @@
     @Test
     fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHeightHelper() {
         mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_DIMENS_REFACTOR)
+        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL)
         setLargeScreen()
         val largeScreenHeaderResourceHeight = 100
         val largeScreenHeaderHelperHeight = 200
@@ -486,6 +492,7 @@
 
     @Test
     fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() {
+        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL)
         setSmallScreen()
         underTest.updateResources()
         assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0)
@@ -506,6 +513,7 @@
 
     @Test
     fun testSinglePaneShadeLayout_isAlignedToParent() {
+        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL)
         disableSplitShade()
         underTest.updateResources()
         assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
index 8be2ef0..452302d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt
@@ -51,7 +51,7 @@
 class SystemEventChipAnimationControllerTest : SysuiTestCase() {
     private lateinit var controller: SystemEventChipAnimationController
 
-    @get:Rule val animatorTestRule = AnimatorTestRule()
+    @get:Rule val animatorTestRule = AnimatorTestRule(this)
     @Mock private lateinit var sbWindowController: StatusBarWindowController
     @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 875fe58..cacfa8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -76,7 +76,7 @@
     private lateinit var chipAnimationController: SystemEventChipAnimationController
     private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler
 
-    @get:Rule val animatorTestRule = AnimatorTestRule()
+    @get:Rule val animatorTestRule = AnimatorTestRule(this)
 
     @Before
     fun setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index 039fef9..82093ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -54,7 +54,7 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
 
-    @get:Rule val animatorTestRule = AnimatorTestRule()
+    @get:Rule val animatorTestRule = AnimatorTestRule(this)
 
     private val kosmos = Kosmos()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
index 7073cc7..85b8b03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt
@@ -15,7 +15,13 @@
  */
 package com.android.systemui.statusbar.notification.collection.coordinator
 
+import android.app.Flags.lifetimeExtensionRefactor
+import android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR
+import android.app.Notification
+import android.app.RemoteInputHistoryItem
 import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.service.notification.StatusBarNotification
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
@@ -34,6 +40,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.captureMany
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -42,6 +49,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.never
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations.initMocks
 
@@ -57,6 +65,7 @@
     private lateinit var entry2: NotificationEntry
 
     @Mock private lateinit var lifetimeExtensionCallback: OnEndLifetimeExtensionCallback
+
     @Mock private lateinit var rebuilder: RemoteInputNotificationRebuilder
     @Mock private lateinit var remoteInputManager: NotificationRemoteInputManager
     @Mock private lateinit var mainHandler: Handler
@@ -84,9 +93,6 @@
         listener = withArgCaptor {
             verify(remoteInputManager).setRemoteInputListener(capture())
         }
-        collectionListener = withArgCaptor {
-            verify(pipeline).addCollectionListener(capture())
-        }
         entry1 = NotificationEntryBuilder().setId(1).build()
         entry2 = NotificationEntryBuilder().setId(2).build()
         `when`(rebuilder.rebuildForCanceledSmartReplies(any())).thenReturn(sbn)
@@ -98,16 +104,23 @@
     val remoteInputHistoryExtender get() = coordinator.mRemoteInputHistoryExtender
     val smartReplyHistoryExtender get() = coordinator.mSmartReplyHistoryExtender
 
+    val collectionListeners get() = captureMany {
+        verify(pipeline, times(1)).addCollectionListener(capture())
+    }
+
     @Test
     fun testRemoteInputActive() {
         `when`(remoteInputManager.isRemoteInputActive(entry1)).thenReturn(true)
         assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isTrue()
-        assertThat(remoteInputHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse()
-        assertThat(smartReplyHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse()
+        if (!lifetimeExtensionRefactor()) {
+            assertThat(remoteInputHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse()
+            assertThat(smartReplyHistoryExtender.maybeExtendLifetime(entry1, 0)).isFalse()
+        }
         assertThat(listener.isNotificationKeptForRemoteInputHistory(entry1.key)).isFalse()
     }
 
     @Test
+    @DisableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
     fun testRemoteInputHistory() {
         `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry1)).thenReturn(true)
         assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isFalse()
@@ -117,6 +130,7 @@
     }
 
     @Test
+    @DisableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
     fun testSmartReplyHistory() {
         `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry1)).thenReturn(true)
         assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isFalse()
@@ -142,4 +156,81 @@
         verify(lifetimeExtensionCallback).onEndLifetimeExtension(remoteInputActiveExtender, entry1)
         assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isFalse()
     }
+
+    @Test
+    @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
+    fun testOnlyRemoteInputActiveLifetimeExtenderExtends() {
+        `when`(remoteInputManager.isRemoteInputActive(entry1)).thenReturn(true)
+        assertThat(remoteInputActiveExtender.maybeExtendLifetime(entry1, 0)).isTrue()
+        assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isTrue()
+
+        listener.onPanelCollapsed()
+        assertThat(remoteInputActiveExtender.isExtending(entry1.key)).isFalse()
+
+        // Checks that lifetimeExtensionCallback is only called the expected number of times,
+        // by the remoteInputActiveExtender.
+        // Checks that the remote input history extender and smart reply history extenders
+        // aren't attached to the pipeline.
+        verify(lifetimeExtensionCallback, times(1)).onEndLifetimeExtension(any(), any())
+    }
+
+    @Test
+    @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
+    fun testRemoteInputLifetimeExtensionListenerTrigger() {
+        // Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag.
+        val entry = NotificationEntryBuilder()
+                .setId(3)
+                .setTag("entry")
+                .setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true)
+                .build()
+        `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(true)
+        `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(false)
+
+        collectionListeners.forEach {
+            it.onEntryUpdated(entry, true)
+        }
+
+        verify(rebuilder, times(1)).rebuildForRemoteInputReply(entry)
+    }
+
+    @Test
+    @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
+    fun testSmartReplyLifetimeExtensionListenerTrigger() {
+        // Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag.
+        val entry = NotificationEntryBuilder()
+                .setId(3)
+                .setTag("entry")
+                .setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, true)
+                .build()
+        `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(false)
+        `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(true)
+        collectionListeners.forEach {
+            it.onEntryUpdated(entry, true)
+        }
+
+
+        verify(rebuilder, times(1)).rebuildForCanceledSmartReplies(entry)
+        verify(smartReplyController, times(1)).stopSending(entry)
+    }
+
+    @Test
+    @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
+    fun testLifetimeExtensionListenerClearsRemoteInputs() {
+        // Create notification with LIFETIME_EXTENDED_BY_DIRECT_REPLY flag.
+        val entry = NotificationEntryBuilder()
+                .setId(3)
+                .setTag("entry")
+                .setFlag(mContext, Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY, false)
+                .build()
+        entry.remoteInputs = ArrayList<RemoteInputHistoryItem>()
+        entry.remoteInputs.add(RemoteInputHistoryItem("Test Text"))
+        `when`(remoteInputManager.shouldKeepForRemoteInputHistory(entry)).thenReturn(false)
+        `when`(remoteInputManager.shouldKeepForSmartReplyHistory(entry)).thenReturn(false)
+
+        collectionListeners.forEach {
+            it.onEntryUpdated(entry, true)
+        }
+
+        assertThat(entry.remoteInputs).isNull()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index bbf9a6b..38698f8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -45,6 +45,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -80,6 +81,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mStaticMockSession = mockitoSession()
+                .strictness(Strictness.WARN)
                 .mockStatic(BurnInHelperKt.class)
                 .mockStatic(LargeScreenHeaderHelper.class)
                 .startMocking();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
index b3708ba..41b959e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java
@@ -26,6 +26,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.flags.FeatureFlags;
@@ -118,6 +119,7 @@
 
     @Test
     public void testAppearResetsTranslation() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_SHADE_MIGRATION_NSSL);
         mController.setupAodIcons(mAodIcons);
         when(mDozeParameters.shouldControlScreenOff()).thenReturn(false);
         mController.appearAodIcons();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index 6eb1c1a..269b70f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -42,6 +42,7 @@
 import org.junit.Test
 import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 
 @SmallTest
@@ -145,6 +146,18 @@
     }
 
     @Test
+    fun onConfigurationChanged_multipleCalls_flagEnabled_updatesWindowHeightMultipleTimes() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
+
+        view.onConfigurationChanged(Configuration())
+        view.onConfigurationChanged(Configuration())
+        view.onConfigurationChanged(Configuration())
+        view.onConfigurationChanged(Configuration())
+
+        verify(windowController, times(4)).refreshStatusBarHeight()
+    }
+
+    @Test
     fun onConfigurationChanged_flagDisabled_doesNotUpdateWindowHeight() {
         mSetFlagsRule.disableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
 
@@ -154,6 +167,18 @@
     }
 
     @Test
+    fun onConfigurationChanged_multipleCalls_flagDisabled_doesNotUpdateWindowHeight() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX)
+
+        view.onConfigurationChanged(Configuration())
+        view.onConfigurationChanged(Configuration())
+        view.onConfigurationChanged(Configuration())
+        view.onConfigurationChanged(Configuration())
+
+        verify(windowController, never()).refreshStatusBarHeight()
+    }
+
+    @Test
     fun onAttachedToWindow_updatesLeftTopRightPaddingsBasedOnInsets() {
         val insets = Insets.of(/* left = */ 10, /* top = */ 20, /* right = */ 30, /* bottom = */ 40)
         whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
@@ -232,6 +257,10 @@
             /* privacyIndicatorBounds = */ PrivacyIndicatorBounds(),
             /* displayShape = */ DisplayShape.NONE,
             /* compatInsetsTypes = */ 0,
-            /* compatIgnoreVisibility = */ false
+            /* compatIgnoreVisibility = */ false,
+            /* typeBoundingRectsMap = */ arrayOf(),
+            /* typeMaxBoundingRectsMap = */ arrayOf(),
+            /* frameWidth = */ 0,
+            /* frameHeight = */ 0
         )
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 54d3607..3da5ab9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -131,7 +131,7 @@
     @Mock
     private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Rule
-    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this);
 
     private List<StatusBarWindowStateListener> mStatusBarWindowStateListeners = new ArrayList<>();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
index 2ce060c..997c00c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
@@ -42,7 +42,7 @@
     private val multiSourceMinAlphaController =
         MultiSourceMinAlphaController(view, initialAlpha = INITIAL_ALPHA)
 
-    @get:Rule val animatorTestRule = AnimatorTestRule()
+    @get:Rule val animatorTestRule = AnimatorTestRule(this)
 
     @Before
     fun setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 658e6b0..13167b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -112,7 +112,7 @@
     private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
 
     @Rule
-    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this);
 
     @Before
     public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 8a33778..25a0bc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -157,7 +157,7 @@
     private FakeSettings mSecureSettings;
 
     @Rule
-    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this);
 
    @Before
     public void setup() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt
index af1d788..e6b4d06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt
@@ -12,12 +12,9 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.TestCoroutineScope
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -41,11 +38,12 @@
     private lateinit var underTest: WalletContextualLocationsService
     private lateinit var testScope: TestScope
     private var listenerRegisteredCount: Int = 0
-    private val listener: IWalletCardsUpdatedListener.Stub = object : IWalletCardsUpdatedListener.Stub() {
-        override fun registerNewWalletCards(cards: List<WalletCard?>) {
-            listenerRegisteredCount++
+    private val listener: IWalletCardsUpdatedListener.Stub =
+        object : IWalletCardsUpdatedListener.Stub() {
+            override fun registerNewWalletCards(cards: List<WalletCard?>) {
+                listenerRegisteredCount++
+            }
         }
-    }
 
     @Before
     @kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,50 +58,56 @@
         featureFlags.set(Flags.ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS, true)
         listenerRegisteredCount = 0
 
-        underTest = WalletContextualLocationsService(controller, featureFlags, testScope.backgroundScope)
+        underTest =
+            WalletContextualLocationsService(controller, featureFlags, testScope.backgroundScope)
     }
 
     @Test
     @kotlinx.coroutines.ExperimentalCoroutinesApi
-    fun addListener() = testScope.runTest {
-        underTest.addWalletCardsUpdatedListenerInternal(listener)
-        assertThat(listenerRegisteredCount).isEqualTo(1)
-  }
+    fun addListener() =
+        testScope.runTest {
+            underTest.addWalletCardsUpdatedListenerInternal(listener)
+            assertThat(listenerRegisteredCount).isEqualTo(1)
+        }
 
     @Test
     @kotlinx.coroutines.ExperimentalCoroutinesApi
-    fun addStoreLocations() = testScope.runTest {
-        underTest.onWalletContextualLocationsStateUpdatedInternal(ArrayList<String>())
-        verify(controller, times(1)).setSuggestionCardIds(anySet())
-    }
+    fun addStoreLocations() =
+        testScope.runTest {
+            underTest.onWalletContextualLocationsStateUpdatedInternal(ArrayList<String>())
+            verify(controller, times(1)).setSuggestionCardIds(anySet())
+        }
 
     @Test
     @kotlinx.coroutines.ExperimentalCoroutinesApi
-    fun updateListenerAndLocationsState() = testScope.runTest {
-        // binds to the service and adds a listener
-        val underTestStub = getInterface
-        underTestStub.addWalletCardsUpdatedListener(listener)
-        assertThat(listenerRegisteredCount).isEqualTo(1)
+    fun updateListenerAndLocationsState() =
+        testScope.runTest {
+            // binds to the service and adds a listener
+            val underTestStub = getInterface
+            underTestStub.addWalletCardsUpdatedListener(listener)
+            assertThat(listenerRegisteredCount).isEqualTo(1)
 
-        // sends a list of card IDs to the controller
-        underTestStub.onWalletContextualLocationsStateUpdated(ArrayList<String>())
-        verify(controller, times(1)).setSuggestionCardIds(anySet())
+            // sends a list of card IDs to the controller
+            underTestStub.onWalletContextualLocationsStateUpdated(ArrayList<String>())
+            verify(controller, times(1)).setSuggestionCardIds(anySet())
 
-        // adds another listener
-        fakeWalletCards.update{ updatedFakeWalletCards }
-        runCurrent()
-        assertThat(listenerRegisteredCount).isEqualTo(2)
+            // adds another listener
+            fakeWalletCards.update { updatedFakeWalletCards }
+            runCurrent()
+            assertThat(listenerRegisteredCount).isEqualTo(2)
 
-        // sends another list of card IDs to the controller
-        underTestStub.onWalletContextualLocationsStateUpdated(ArrayList<String>())
-        verify(controller, times(2)).setSuggestionCardIds(anySet())
-    }
+            // sends another list of card IDs to the controller
+            underTestStub.onWalletContextualLocationsStateUpdated(ArrayList<String>())
+            verify(controller, times(2)).setSuggestionCardIds(anySet())
+        }
 
     private val fakeWalletCards: MutableStateFlow<List<WalletCard>>
         get() {
             val intent = Intent(getContext(), WalletContextualLocationsService::class.java)
-            val pi: PendingIntent = PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE)
-            val icon: Icon = Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888))
+            val pi: PendingIntent =
+                PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE)
+            val icon: Icon =
+                Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888))
             val walletCards: ArrayList<WalletCard> = ArrayList<WalletCard>()
             walletCards.add(WalletCard.Builder("card1", icon, "card", pi).build())
             walletCards.add(WalletCard.Builder("card2", icon, "card", pi).build())
@@ -113,8 +117,10 @@
     private val updatedFakeWalletCards: List<WalletCard>
         get() {
             val intent = Intent(getContext(), WalletContextualLocationsService::class.java)
-            val pi: PendingIntent = PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE)
-            val icon: Icon = Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888))
+            val pi: PendingIntent =
+                PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE)
+            val icon: Icon =
+                Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888))
             val walletCards: ArrayList<WalletCard> = ArrayList<WalletCard>()
             walletCards.add(WalletCard.Builder("card3", icon, "card", pi).build())
             return walletCards
@@ -125,4 +131,4 @@
             val intent = Intent()
             return IWalletContextualLocationsService.Stub.asInterface(underTest.onBind(intent))
         }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
index 1afe56f..5860c2d 100644
--- a/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
+++ b/packages/SystemUI/tests/utils/src/android/animation/AnimatorTestRule.java
@@ -16,14 +16,21 @@
 
 package android.animation;
 
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
 import android.animation.AnimationHandler.AnimationFrameCallback;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunnableWithException;
 import android.util.AndroidRuntimeException;
 import android.util.Singleton;
 import android.view.Choreographer;
+import android.view.animation.AnimationUtils;
+
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.util.Preconditions;
 
@@ -74,25 +81,31 @@
             return new TestHandler();
         }
     };
+    private final Object mTest;
     private final long mStartTime;
     private long mTotalTimeDelta = 0;
+    private volatile boolean mCanLockAnimationClock;
+    private Looper mLooperWithLockedAnimationClock;
 
     /**
-     * Construct an AnimatorTestRule with a custom start time.
-     * @see #AnimatorTestRule()
+     * Construct an AnimatorTestRule with access to the test instance and a custom start time.
+     * @see #AnimatorTestRule(Object)
      */
-    public AnimatorTestRule(long startTime) {
+    public AnimatorTestRule(Object test, long startTime) {
+        mTest = test;
         mStartTime = startTime;
     }
 
     /**
-     * Construct an AnimatorTestRule with a start time of {@link SystemClock#uptimeMillis()}.
-     * Initializing the start time with this clock reduces the discrepancies with various internals
-     * of classes like ValueAnimator which can sometimes read that clock via
-     * {@link android.view.animation.AnimationUtils#currentAnimationTimeMillis()}.
+     * Construct an AnimatorTestRule for the given test instance with a start time of
+     * {@link SystemClock#uptimeMillis()}. Initializing the start time with this clock reduces the
+     * discrepancies with various internals of classes like ValueAnimator which can sometimes read
+     * that clock via {@link android.view.animation.AnimationUtils#currentAnimationTimeMillis()}.
+     *
+     * @param test the test instance used to access the {@link TestableLooper} used by the class.
      */
-    public AnimatorTestRule() {
-        this(SystemClock.uptimeMillis());
+    public AnimatorTestRule(Object test) {
+        this(test, SystemClock.uptimeMillis());
     }
 
     @NonNull
@@ -102,10 +115,16 @@
             @Override
             public void evaluate() throws Throwable {
                 final TestHandler testHandler = mTestHandler.get();
-                AnimationHandler objAtStart = AnimationHandler.setTestHandler(testHandler);
+                final AnimationHandler objAtStart = AnimationHandler.setTestHandler(testHandler);
+                final RunnableWithException lockClock =
+                        wrapWithRunBlocking(new LockAnimationClockRunnable());
+                final RunnableWithException unlockClock =
+                        wrapWithRunBlocking(new UnlockAnimationClockRunnable());
                 try {
+                    lockClock.run();
                     base.evaluate();
                 } finally {
+                    unlockClock.run();
                     AnimationHandler objAtEnd = AnimationHandler.setTestHandler(objAtStart);
                     if (testHandler != objAtEnd) {
                         // pass or fail, inner logic not restoring the handler needs to be reported.
@@ -118,6 +137,78 @@
         };
     }
 
+    private RunnableWithException wrapWithRunBlocking(RunnableWithException runnable) {
+        RunnableWithException wrapped = TestableLooper.wrapWithRunBlocking(mTest, runnable);
+        if (wrapped != null) {
+            return wrapped;
+        }
+        return () -> runOnMainThrowing(runnable);
+    }
+
+    private static void runOnMainThrowing(RunnableWithException runnable) throws Exception {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            runnable.run();
+        } else {
+            final Throwable[] throwableBox = new Throwable[1];
+            InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+                try {
+                    runnable.run();
+                } catch (Throwable t) {
+                    throwableBox[0] = t;
+                }
+            });
+            if (throwableBox[0] == null) {
+                return;
+            } else if (throwableBox[0] instanceof RuntimeException ex) {
+                throw ex;
+            } else if (throwableBox[0] instanceof Error err) {
+                throw err;
+            } else {
+                throw new RuntimeException(throwableBox[0]);
+            }
+        }
+    }
+
+    private class LockAnimationClockRunnable implements RunnableWithException {
+        @Override
+        public void run() {
+            mLooperWithLockedAnimationClock = Looper.myLooper();
+            mCanLockAnimationClock = true;
+            lockAnimationClockToCurrentTime();
+        }
+    }
+
+    private class UnlockAnimationClockRunnable implements RunnableWithException {
+        @Override
+        public void run() {
+            mCanLockAnimationClock = false;
+            mLooperWithLockedAnimationClock = null;
+            AnimationUtils.unlockAnimationClock();
+        }
+    }
+
+    private void lockAnimationClockToCurrentTime() {
+        if (!mCanLockAnimationClock) {
+            throw new AssertionError("Unable to lock the animation clock; "
+                    + "has the test started? already finished?");
+        }
+        if (mLooperWithLockedAnimationClock != Looper.myLooper()) {
+            throw new AssertionError("Animation clock being locked on " + Looper.myLooper()
+                    + " but should only be locked on " + mLooperWithLockedAnimationClock);
+        }
+        long desiredTime = getCurrentTime();
+        AnimationUtils.lockAnimationClock(desiredTime);
+        if (!mCanLockAnimationClock) {
+            AnimationUtils.unlockAnimationClock();
+            throw new AssertionError("Threading error when locking the animation clock");
+        }
+        long outputTime = AnimationUtils.currentAnimationTimeMillis();
+        if (outputTime != desiredTime) {
+            throw new AssertionError("currentAnimationTimeMillis() is " + outputTime
+                    + " after locking to " + desiredTime);
+        }
+    }
+
     /**
      * If any new {@link Animator}s have been registered since the last time the frame time was
      * advanced, initialize them with the current frame time.  Failing to do this will result in the
@@ -178,6 +269,7 @@
             // advance time
             mTotalTimeDelta += timeDelta;
         }
+        lockAnimationClockToCurrentTime();
         if (preFrameAction != null) {
             preFrameAction.accept(timeDelta);
             // After letting other code run, clear any new callbacks to avoid double-ticking them
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
index ba9c5ed..e2fc44f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/AnimatorTestRule.kt
@@ -26,12 +26,16 @@
  * A rule that wraps both [androidx.core.animation.AnimatorTestRule] and
  * [android.animation.AnimatorTestRule] such that the clocks of the two animation handlers can be
  * advanced together.
+ *
+ * @param test the instance of the test used to look up the TestableLooper.  If a TestableLooper is
+ * found, the time can only be advanced on that thread; otherwise the time must be advanced on the
+ * main thread.
  */
-class AnimatorTestRule : TestRule {
+class AnimatorTestRule(test: Any?) : TestRule {
     // Create the androidx rule, which initializes start time to SystemClock.uptimeMillis(),
     // then copy that time to the platform rule so that the two clocks are in sync.
     private val androidxRule = androidx.core.animation.AnimatorTestRule()
-    private val platformRule = android.animation.AnimatorTestRule(androidxRule.startTime)
+    private val platformRule = android.animation.AnimatorTestRule(test, androidxRule.startTime)
     private val advanceAndroidXTimeBy =
         Consumer<Long> { timeDelta -> androidxRule.advanceTimeBy(timeDelta) }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
new file mode 100644
index 0000000..c208aad
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.scene.shared.model
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeSceneDataSource(
+    initialSceneKey: SceneKey,
+) : SceneDataSource {
+
+    private val _currentScene = MutableStateFlow(initialSceneKey)
+    override val currentScene: StateFlow<SceneKey> = _currentScene.asStateFlow()
+
+    var isPaused = false
+        private set
+    var pendingScene: SceneKey? = null
+        private set
+
+    override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) {
+        if (isPaused) {
+            pendingScene = toScene
+        } else {
+            _currentScene.value = toScene
+        }
+    }
+
+    /**
+     * Pauses scene changes.
+     *
+     * Any following calls to [changeScene] will be conflated and the last one will be remembered.
+     */
+    fun pause() {
+        check(!isPaused) { "Can't pause what's already paused!" }
+
+        isPaused = true
+    }
+
+    /**
+     * Unpauses scene changes.
+     *
+     * If there were any calls to [changeScene] since [pause] was called, the latest of the bunch
+     * will be replayed.
+     *
+     * If [force] is `true`, there will be no check that [isPaused] is true.
+     *
+     * If [expectedScene] is provided, will assert that it's indeed the latest called.
+     */
+    fun unpause(
+        force: Boolean = false,
+        expectedScene: SceneKey? = null,
+    ) {
+        check(force || isPaused) { "Can't unpause what's already not paused!" }
+
+        isPaused = false
+        pendingScene?.let { _currentScene.value = it }
+        pendingScene = null
+
+        check(expectedScene == null || currentScene.value == expectedScene) {
+            """
+                Unexpected scene while unpausing.
+                Expected $expectedScene but was $currentScene.
+            """
+                .trimIndent()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt
new file mode 100644
index 0000000..f519686
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/SceneDataSourceKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.scene.shared.model
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.initialSceneKey
+import com.android.systemui.scene.sceneContainerConfig
+
+val Kosmos.fakeSceneDataSource by Fixture {
+    FakeSceneDataSource(
+        initialSceneKey = initialSceneKey,
+    )
+}
+
+val Kosmos.sceneDataSourceDelegator by Fixture {
+    SceneDataSourceDelegator(
+            applicationScope = applicationCoroutineScope,
+            config = sceneContainerConfig,
+        )
+        .apply { setDelegate(fakeSceneDataSource) }
+}
+
+val Kosmos.sceneDataSource by Fixture { sceneDataSourceDelegator }
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 1ac69f6..35ce481 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -58,11 +58,14 @@
     visibility: ["//visibility:public"],
 }
 
-java_host_for_device {
-    name: "core-xml-for-device",
-    libs: [
-        "core-xml-for-host",
+java_library {
+    // Prefixed with "200" to ensure it's sorted early in Tradefed classpath
+    // so that we provide a concrete implementation before Mainline stubs
+    name: "200-kxml2-android",
+    static_libs: [
+        "kxml2-android",
     ],
+    visibility: ["//frameworks/base"],
 }
 
 java_host_for_device {
diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING
index 0319848..a5b28ad 100644
--- a/ravenwood/TEST_MAPPING
+++ b/ravenwood/TEST_MAPPING
@@ -2,6 +2,9 @@
   "presubmit": [
     {
       "name": "RavenwoodMockitoTest_device"
+    },
+    {
+      "name": "RavenwoodBivalentTest_device"
     }
   ],
   "ravenwood-presubmit": [
@@ -16,6 +19,14 @@
     {
       "name": "CtsUtilTestCasesRavenwood",
       "host": true
+    },
+    {
+      "name": "RavenwoodCoreTest",
+      "host": true
+    },
+    {
+      "name": "RavenwoodBivalentTest",
+      "host": true
     }
   ]
 }
diff --git a/ravenwood/bivalenttest/Android.bp b/ravenwood/bivalenttest/Android.bp
new file mode 100644
index 0000000..acf8533
--- /dev/null
+++ b/ravenwood/bivalenttest/Android.bp
@@ -0,0 +1,47 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_ravenwood_test {
+    name: "RavenwoodBivalentTest",
+
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+    ],
+    srcs: [
+        "test/**/*.java",
+    ],
+    sdk_version: "test_current",
+    auto_gen_config: true,
+}
+
+android_test {
+    name: "RavenwoodBivalentTest_device",
+
+    srcs: [
+        "test/**/*.java",
+    ],
+    static_libs: [
+        "junit",
+        "truth",
+
+        "androidx.annotation_annotation",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+
+        "ravenwood-junit",
+    ],
+    test_suites: [
+        "device-tests",
+    ],
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/ravenwood/bivalenttest/AndroidManifest.xml b/ravenwood/bivalenttest/AndroidManifest.xml
new file mode 100644
index 0000000..474c03f
--- /dev/null
+++ b/ravenwood/bivalenttest/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.ravenwood.bivalenttest">
+
+    <application android:debuggable="true" >
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.ravenwood.bivalenttest"
+        />
+</manifest>
diff --git a/ravenwood/bivalenttest/AndroidTest.xml b/ravenwood/bivalenttest/AndroidTest.xml
new file mode 100644
index 0000000..ac4695b
--- /dev/null
+++ b/ravenwood/bivalenttest/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs Frameworks Services Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="RavenwoodBivalentTest_device.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.ravenwood.bivalenttest" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/ravenwood/bivalenttest/README.md b/ravenwood/bivalenttest/README.md
new file mode 100644
index 0000000..7153555
--- /dev/null
+++ b/ravenwood/bivalenttest/README.md
@@ -0,0 +1,3 @@
+# Ravenwood bivalent test
+
+This test contains bivalent tests for Ravenwood itself.
\ No newline at end of file
diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java
new file mode 100644
index 0000000..4b650b4
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodRuleTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.platform.test.ravenwood.bivalenttest;
+
+import android.platform.test.annotations.DisabledOnNonRavenwood;
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RavenwoodRuleTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    @Test
+    @DisabledOnRavenwood
+    public void testDeviceOnly() {
+        Assert.assertFalse(RavenwoodRule.isOnRavenwood());
+    }
+
+    @Test
+    @DisabledOnNonRavenwood
+    public void testRavenwoodOnly() {
+        Assert.assertTrue(RavenwoodRule.isOnRavenwood());
+    }
+
+    // TODO: Add more tests
+}
diff --git a/ravenwood/coretest/Android.bp b/ravenwood/coretest/Android.bp
index 9b7f8f7..a78c5c1 100644
--- a/ravenwood/coretest/Android.bp
+++ b/ravenwood/coretest/Android.bp
@@ -12,9 +12,9 @@
 
     static_libs: [
         "androidx.annotation_annotation",
+        "androidx.test.ext.junit",
         "androidx.test.rules",
     ],
-
     srcs: [
         "test/**/*.java",
     ],
diff --git a/ravenwood/coretest/README.md b/ravenwood/coretest/README.md
new file mode 100644
index 0000000..b60bfbf
--- /dev/null
+++ b/ravenwood/coretest/README.md
@@ -0,0 +1,3 @@
+# Ravenwood core test
+
+This test contains (non-bivalent) tests for Ravenwood itself -- e.g. tests for the ravenwood rules.
\ No newline at end of file
diff --git a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java b/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
index e58c282..2cd585f 100644
--- a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
+++ b/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java
@@ -17,7 +17,7 @@
 
 import android.platform.test.ravenwood.RavenwoodRule;
 
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.runner.AndroidJUnit4; // Intentionally use the deprecated one.
 
 import org.junit.Assume;
 import org.junit.Rule;
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index 0229611..2eef0cd 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -89,6 +89,7 @@
 
 # Internals
 class com.android.internal.util.FileRotator stubclass
+class com.android.internal.util.FastXmlSerializer stubclass
 class com.android.internal.util.HexDump stubclass
 class com.android.internal.util.MessageUtils stubclass
 class com.android.internal.util.Preconditions stubclass
diff --git a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
new file mode 100644
index 0000000..8ca34ba
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Tests marked with this annotation are only executed when running on Ravenwood, but not
+ * on a device.
+ *
+ * This is basically equivalent to the opposite of {@link DisabledOnRavenwood}, but in order to
+ * avoid complex structure, and there's no equivalent to the opposite {@link EnabledOnRavenwood},
+ * which means if a test class has this annotation, you can't negate it in subclasses or
+ * on a per-method basis.
+ *
+ * The {@code RAVENWOOD_RUN_DISABLED_TESTS} environmental variable won't work because it won't be
+ * propagated to the device. (We may support it in the future, possibly using a debug. sysprop.)
+ *
+ * @hide
+ */
+@Inherited
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DisabledOnNonRavenwood {
+    /**
+     * General free-form description of why this test is being ignored.
+     */
+    String reason() default "";
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
index 8d76970..9a4d488 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java
@@ -18,6 +18,7 @@
 
 import static android.platform.test.ravenwood.RavenwoodRule.ENABLE_PROBE_IGNORED;
 import static android.platform.test.ravenwood.RavenwoodRule.IS_ON_RAVENWOOD;
+import static android.platform.test.ravenwood.RavenwoodRule.shouldEnableOnDevice;
 import static android.platform.test.ravenwood.RavenwoodRule.shouldEnableOnRavenwood;
 import static android.platform.test.ravenwood.RavenwoodRule.shouldStillIgnoreInProbeIgnoreMode;
 
@@ -42,6 +43,7 @@
     public Statement apply(Statement base, Description description) {
         // No special treatment when running outside Ravenwood; run tests as-is
         if (!IS_ON_RAVENWOOD) {
+            Assume.assumeTrue(shouldEnableOnDevice(description));
             return base;
         }
 
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 764573d..b90f112 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -22,6 +22,7 @@
 
 import static org.junit.Assert.fail;
 
+import android.platform.test.annotations.DisabledOnNonRavenwood;
 import android.platform.test.annotations.DisabledOnRavenwood;
 import android.platform.test.annotations.EnabledOnRavenwood;
 import android.platform.test.annotations.IgnoreUnderRavenwood;
@@ -211,6 +212,15 @@
         return IS_ON_RAVENWOOD;
     }
 
+    static boolean shouldEnableOnDevice(Description description) {
+        if (description.isTest()) {
+            if (description.getAnnotation(DisabledOnNonRavenwood.class) != null) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     /**
      * Determine if the given {@link Description} should be enabled when running on the
      * Ravenwood test environment.
@@ -271,6 +281,7 @@
     public Statement apply(Statement base, Description description) {
         // No special treatment when running outside Ravenwood; run tests as-is
         if (!IS_ON_RAVENWOOD) {
+            Assume.assumeTrue(shouldEnableOnDevice(description));
             return base;
         }
 
diff --git a/ravenwood/minimum-test/README.md b/ravenwood/minimum-test/README.md
new file mode 100644
index 0000000..6b0abe9
--- /dev/null
+++ b/ravenwood/minimum-test/README.md
@@ -0,0 +1,3 @@
+# Ravenwood minimum test
+
+This directory contains a minimum possible ravenwood test -- no extra dependencies, etc.
\ No newline at end of file
diff --git a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
index 03cfad5..b477117 100644
--- a/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
+++ b/ravenwood/minimum-test/test/com/android/ravenwood/RavenwoodMinimumTest.java
@@ -28,7 +28,7 @@
 @RunWith(AndroidJUnit4.class)
 public class RavenwoodMinimumTest {
     @Rule
-    public RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
             .setProcessApp()
             .build();
 
diff --git a/ravenwood/mockito/Android.bp b/ravenwood/mockito/Android.bp
index a74bca4..95c7394 100644
--- a/ravenwood/mockito/Android.bp
+++ b/ravenwood/mockito/Android.bp
@@ -13,6 +13,9 @@
     srcs: [
         "test/**/*.java",
     ],
+    exclude_srcs: [
+        "test/**/*DeviceOnly*.java",
+    ],
     static_libs: [
         "junit",
         "truth",
@@ -31,6 +34,9 @@
     srcs: [
         "test/**/*.java",
     ],
+    exclude_srcs: [
+        "test/**/*RavenwoodOnly*.java",
+    ],
     static_libs: [
         "junit",
         "truth",
diff --git a/ravenwood/mockito/AndroidTest.xml b/ravenwood/mockito/AndroidTest.xml
index 96bc275..5ba9b1f 100644
--- a/ravenwood/mockito/AndroidTest.xml
+++ b/ravenwood/mockito/AndroidTest.xml
@@ -22,8 +22,6 @@
         <option name="test-file-name" value="RavenwoodMockitoTest_device.apk" />
     </target_preparer>
 
-    <option name="test-tag" value="FrameworksMockingServicesTests" />
-
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.ravenwood.mockitotest" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
diff --git a/ravenwood/mockito/README.md b/ravenwood/mockito/README.md
new file mode 100644
index 0000000..4ceb795
--- /dev/null
+++ b/ravenwood/mockito/README.md
@@ -0,0 +1,3 @@
+# Ravenwood mockito test
+
+This directory contains a sample bivalent test using Mockito.
\ No newline at end of file
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoDeviceOnlyTest.java b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoDeviceOnlyTest.java
new file mode 100644
index 0000000..d02fe69
--- /dev/null
+++ b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoDeviceOnlyTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ravenwood.mockito;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.ActivityManager;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.quality.Strictness;
+
+public class RavenwoodMockitoDeviceOnlyTest {
+    @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    @Test
+    public void testStaticMockOnDevice() {
+        var mockingSession = ExtendedMockito.mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .mockStatic(ActivityManager.class)
+                .startMocking();
+        try {
+            ExtendedMockito.doReturn(true).when(ActivityManager::isUserAMonkey);
+
+            assertThat(ActivityManager.isUserAMonkey()).isEqualTo(true);
+        } finally {
+            mockingSession.finishMocking();
+        }
+    }
+}
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoRavenwoodOnlyTest.java b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
new file mode 100644
index 0000000..0c137d5
--- /dev/null
+++ b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoRavenwoodOnlyTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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.ravenwood.mockito;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.ActivityManager;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+public class RavenwoodMockitoRavenwoodOnlyTest {
+    @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+    @Test
+    public void testStaticMockOnRavenwood() {
+        try (MockedStatic<ActivityManager> am = Mockito.mockStatic(ActivityManager.class)) {
+            am.when(ActivityManager::isUserAMonkey).thenReturn(true);
+            assertThat(ActivityManager.isUserAMonkey()).isEqualTo(true);
+        }
+    }
+}
diff --git a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
index 1284d64..9566710 100644
--- a/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
+++ b/ravenwood/mockito/test/com/android/ravenwood/mockito/RavenwoodMockitoTest.java
@@ -31,28 +31,6 @@
 public class RavenwoodMockitoTest {
     @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
-
-// Use this to mock static methods, which isn't supported by mockito 2.
-// Mockito supports static mocking since 3.4.0:
-// See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#48
-
-//    private MockitoSession mMockingSession;
-//
-//    @Before
-//    public void setUp() {
-//        mMockingSession = mockitoSession()
-//                .strictness(Strictness.LENIENT)
-//                .mockStatic(RavenwoodMockitoTest.class)
-//                .startMocking();
-//    }
-//
-//    @After
-//    public void tearDown() {
-//        if (mMockingSession != null) {
-//            mMockingSession.finishMocking();
-//        }
-//    }
-
     @Test
     public void testMockJdkClass() {
         Process object = mock(Process.class);
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
index 2c50389..df4e699 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionManagerService.java
@@ -35,6 +35,7 @@
 import android.content.pm.ParceledListSlice;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.Process;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
@@ -162,6 +163,13 @@
                     (service) -> service.onDestroyPredictionSessionLocked(sessionId));
         }
 
+        @Override
+        public void requestServiceFeatures(@NonNull AppPredictionSessionId sessionId,
+                IRemoteCallback callback) {
+            runForUserLocked("requestServiceFeatures", sessionId,
+                    (service) -> service.requestServiceFeaturesLocked(sessionId, callback));
+        }
+
         public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
                 @Nullable FileDescriptor err,
                 @NonNull String[] args, @Nullable ShellCallback callback,
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index 84707a8..a0198f2 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -31,6 +31,7 @@
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ServiceInfo;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.provider.DeviceConfig;
@@ -237,6 +238,18 @@
         sessionInfo.destroy();
     }
 
+    /**
+     * Requests the service to provide AppPredictionService features info.
+     */
+    @GuardedBy("mLock")
+    public void requestServiceFeaturesLocked(@NonNull AppPredictionSessionId sessionId,
+            @NonNull IRemoteCallback callback) {
+        final AppPredictionSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, true, sessionInfo.mUsesPeopleService,
+                s -> s.requestServiceFeatures(sessionId, callback));
+    }
+
     @Override
     public void onFailureOrTimeout(boolean timedOut) {
         if (isDebug()) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index cd45b03..b8f6b3f 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -480,9 +480,14 @@
     /**
      * The available ANR timers.
      */
+    // ActivityManagerConstants.SERVICE_TIMEOUT/ActivityManagerConstants.SERVICE_BACKGROUND_TIMEOUT
     private final ProcessAnrTimer mActiveServiceAnrTimer;
+    // see ServiceRecord$ShortFgsInfo#getAnrTime()
     private final ServiceAnrTimer mShortFGSAnrTimer;
+    // ActivityManagerConstants.DEFAULT_SERVICE_START_FOREGROUND_TIMEOUT_MS
     private final ServiceAnrTimer mServiceFGAnrTimer;
+    // see ServiceRecord#getEarliestStopTypeAndTime()
+    private final ServiceAnrTimer mFGSAnrTimer;
 
     // allowlisted packageName.
     ArraySet<String> mAllowListWhileInUsePermissionInFgs = new ArraySet<>();
@@ -744,10 +749,13 @@
                 "SERVICE_TIMEOUT");
         this.mShortFGSAnrTimer = new ServiceAnrTimer(service,
                 ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG,
-                "FGS_TIMEOUT");
+                "SHORT_FGS_TIMEOUT");
         this.mServiceFGAnrTimer = new ServiceAnrTimer(service,
                 ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG,
                 "SERVICE_FOREGROUND_TIMEOUT");
+        this.mFGSAnrTimer = new ServiceAnrTimer(service,
+                ActivityManagerService.SERVICE_FGS_ANR_TIMEOUT_MSG,
+                "FGS_TIMEOUT");
     }
 
     void systemServicesReady() {
@@ -1570,6 +1578,7 @@
             }
 
             maybeStopShortFgsTimeoutLocked(service);
+            maybeStopFgsTimeoutLocked(service);
 
             final int uid = service.appInfo.uid;
             final String packageName = service.name.getPackageName();
@@ -1758,6 +1767,7 @@
             }
 
             maybeStopShortFgsTimeoutLocked(r);
+            maybeStopFgsTimeoutLocked(r);
 
             final int uid = r.appInfo.uid;
             final String packageName = r.name.getPackageName();
@@ -2243,6 +2253,8 @@
 
                 // Whether to extend the SHORT_SERVICE time out.
                 boolean extendShortServiceTimeout = false;
+                // Whether to extend the timeout for a time-limited FGS type.
+                boolean extendFgsTimeout = false;
 
                 // Whether setFgsRestrictionLocked() is called in here. Only used for logging.
                 boolean fgsRestrictionRecalculated = false;
@@ -2287,6 +2299,19 @@
                     final boolean isOldTypeShortFgsAndTimedOut =
                             r.shouldTriggerShortFgsTimeout(nowUptime);
 
+                    // Calling startForeground on a FGS type which has a time limit will only be
+                    // allowed if the app is in a state where it can normally start another FGS.
+                    // The timeout will behave as follows:
+                    // A) <TIME_LIMITED_TYPE> -> another <TIME_LIMITED_TYPE>
+                    //    - If the start succeeds, the timeout is reset.
+                    // B) <TIME_LIMITED_TYPE> -> non-time-limited type
+                    //    - If the start succeeds, the timeout will stop.
+                    // C) non-time-limited type -> <TIME_LIMITED_TYPE>
+                    //    - If the start succeeds, the timeout will start.
+                    final boolean isOldTypeTimeLimited = r.isFgsTimeLimited();
+                    final boolean isNewTypeTimeLimited =
+                            r.canFgsTypeTimeOut(foregroundServiceType);
+
                     // If true, we skip the BFSL check.
                     boolean bypassBfslCheck = false;
 
@@ -2355,6 +2380,35 @@
                                 // "if (r.mAllowStartForeground == REASON_DENIED...)" block below.
                             }
                         }
+                    } else if (r.isForeground && isOldTypeTimeLimited) {
+
+                        // See if the app could start an FGS or not.
+                        r.clearFgsAllowStart();
+                        setFgsRestrictionLocked(r.serviceInfo.packageName, r.app.getPid(),
+                                r.appInfo.uid, r.intent.getIntent(), r, r.userId,
+                                BackgroundStartPrivileges.NONE, false /* isBindService */);
+                        fgsRestrictionRecalculated = true;
+
+                        final boolean fgsStartAllowed = !isBgFgsRestrictionEnabledForService
+                                                            || r.isFgsAllowedStart();
+
+                        if (fgsStartAllowed) {
+                            if (isNewTypeTimeLimited) {
+                                // Note: in the future, we may want to look into metrics to see if
+                                // apps are constantly switching between a time-limited type and a
+                                // non-time-limited type or constantly calling startForeground()
+                                // opportunistically on the same type to gain runtime and apply the
+                                // stricter timeout. For now, always extend the timeout if the app
+                                // is in a state where it's allowed to start a FGS.
+                                extendFgsTimeout = true;
+                            } else {
+                                // FGS type is changing from a time-restricted type to one without
+                                // a time limit so proceed as normal.
+                                // The timeout will stop later, in maybeUpdateFgsTrackingLocked().
+                            }
+                        } else {
+                            // This case will be handled in the BFSL check below.
+                        }
                     } else if (r.mStartForegroundCount == 0) {
                         /*
                         If the service was started with startService(), not
@@ -2596,6 +2650,7 @@
 
                     maybeUpdateShortFgsTrackingLocked(r,
                             extendShortServiceTimeout);
+                    maybeUpdateFgsTrackingLocked(r, extendFgsTimeout);
                 } else {
                     if (DEBUG_FOREGROUND_SERVICE) {
                         Slog.d(TAG, "Suppressing startForeground() for FAS " + r);
@@ -2631,6 +2686,7 @@
                 }
 
                 maybeStopShortFgsTimeoutLocked(r);
+                maybeStopFgsTimeoutLocked(r);
 
                 // Adjust notification handling before setting isForeground to false, because
                 // that state is relevant to the notification policy side.
@@ -3608,6 +3664,116 @@
         }
     }
 
+    void onFgsTimeout(ServiceRecord sr) {
+        synchronized (mAm) {
+            final long nowUptime = SystemClock.uptimeMillis();
+            final int fgsType = sr.getTimedOutFgsType(nowUptime);
+            if (fgsType == -1) {
+                mFGSAnrTimer.discard(sr);
+                return;
+            }
+            Slog.e(TAG_SERVICE, "FGS (" + ServiceInfo.foregroundServiceTypeToLabel(fgsType)
+                    + ") timed out: " + sr);
+            mFGSAnrTimer.accept(sr);
+            traceInstant("FGS timed out: ", sr);
+
+            logFGSStateChangeLocked(sr,
+                    FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT,
+                    nowUptime > sr.mFgsEnterTime ? (int) (nowUptime - sr.mFgsEnterTime) : 0,
+                    FGS_STOP_REASON_UNKNOWN,
+                    FGS_TYPE_POLICY_CHECK_UNKNOWN,
+                    FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA,
+                    false /* fgsRestrictionRecalculated */
+            );
+            try {
+                sr.app.getThread().scheduleTimeoutServiceForType(sr, sr.getLastStartId(), fgsType);
+            } catch (RemoteException e) {
+                Slog.w(TAG_SERVICE, "Exception from scheduleTimeoutServiceForType: " + e);
+            }
+
+            // ANR the service after giving the service some time to clean up.
+            // ServiceRecord.getEarliestStopTypeAndTime() is an absolute time with a reference that
+            // is not "now". Compute the time from "now" when starting the anr timer.
+            final long anrTime = sr.getEarliestStopTypeAndTime().second
+                    + mAm.mConstants.mFgsAnrExtraWaitDuration - SystemClock.uptimeMillis();
+            mFGSAnrTimer.start(sr, anrTime);
+        }
+    }
+
+    private void maybeUpdateFgsTrackingLocked(ServiceRecord sr, boolean extendTimeout) {
+        if (!sr.isFgsTimeLimited()) {
+            // Reset timers if they exist.
+            sr.setIsFgsTimeLimited(false);
+            mFGSAnrTimer.cancel(sr);
+            mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+            return;
+        }
+
+        if (extendTimeout || !sr.wasFgsPreviouslyTimeLimited()) {
+            traceInstant("FGS start: ", sr);
+            sr.setIsFgsTimeLimited(true);
+
+            // We'll restart the timeout.
+            mFGSAnrTimer.cancel(sr);
+            mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+
+            final Message msg = mAm.mHandler.obtainMessage(
+                    ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+            mAm.mHandler.sendMessageAtTime(msg, sr.getEarliestStopTypeAndTime().second);
+        }
+    }
+
+    private void maybeStopFgsTimeoutLocked(ServiceRecord sr) {
+        sr.setIsFgsTimeLimited(false); // reset fgs boolean holding time-limited type state.
+        if (!sr.isFgsTimeLimited()) {
+            return; // if none of the types are time-limited, return.
+        }
+        Slog.d(TAG_SERVICE, "Stop FGS timeout: " + sr);
+        mFGSAnrTimer.cancel(sr);
+        mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr);
+    }
+
+    boolean hasServiceTimedOutLocked(ComponentName className, IBinder token) {
+        final int userId = UserHandle.getCallingUserId();
+        final long ident = mAm.mInjector.clearCallingIdentity();
+        try {
+            ServiceRecord sr = findServiceLocked(className, token, userId);
+            if (sr == null) {
+                return false;
+            }
+            final long nowUptime = SystemClock.uptimeMillis();
+            return sr.getTimedOutFgsType(nowUptime) != -1;
+        } finally {
+            mAm.mInjector.restoreCallingIdentity(ident);
+        }
+    }
+
+    void onFgsAnrTimeout(ServiceRecord sr) {
+        final long nowUptime = SystemClock.uptimeMillis();
+        final int fgsType = sr.getTimedOutFgsType(nowUptime);
+        if (fgsType == -1 || !sr.wasFgsPreviouslyTimeLimited()) {
+            return; // no timed out FGS type was found
+        }
+        final String reason = "A foreground service of type "
+                + ServiceInfo.foregroundServiceTypeToLabel(fgsType)
+                + " did not stop within a timeout: " + sr.getComponentName();
+
+        final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason);
+
+        tr.mLatencyTracker.waitingOnAMSLockStarted();
+        synchronized (mAm) {
+            tr.mLatencyTracker.waitingOnAMSLockEnded();
+
+            Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr);
+            traceInstant("FGS ANR: ", sr);
+            mAm.appNotResponding(sr.app, tr);
+
+            // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR
+            // dialog really doesn't remember the "cause" (especially if there have been multiple
+            // ANRs), so it's not doable.
+        }
+    }
+
     private void updateAllowlistManagerLocked(ProcessServiceRecord psr) {
         psr.mAllowlistManager = false;
         for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
@@ -3621,6 +3787,7 @@
 
     private void stopServiceAndUpdateAllowlistManagerLocked(ServiceRecord service) {
         maybeStopShortFgsTimeoutLocked(service);
+        maybeStopFgsTimeoutLocked(service);
         final ProcessServiceRecord psr = service.app.mServices;
         psr.stopService(service);
         psr.updateBoundClientUids();
@@ -5893,6 +6060,7 @@
             Slog.w(TAG_SERVICE, "Short FGS brought down without stopping: " + r);
             maybeStopShortFgsTimeoutLocked(r);
         }
+        maybeStopFgsTimeoutLocked(r);
 
         // Report to all of the connections that the service is no longer
         // available.
@@ -6015,6 +6183,7 @@
         final boolean exitingFg = r.isForeground;
         if (exitingFg) {
             maybeStopShortFgsTimeoutLocked(r);
+            maybeStopFgsTimeoutLocked(r);
             decActiveForegroundAppLocked(smap, r);
             synchronized (mAm.mProcessStats.mLock) {
                 ServiceState stracker = r.getTracker();
@@ -8643,7 +8812,7 @@
             event = EventLogTags.AM_FOREGROUND_SERVICE_DENIED;
         } else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT) {
             event = EventLogTags.AM_FOREGROUND_SERVICE_TIMED_OUT;
-        }else {
+        } else {
             // Unknown event.
             return;
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 72e62c3..d97731c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -1052,6 +1052,27 @@
     public volatile long mShortFgsProcStateExtraWaitDuration =
             DEFAULT_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION;
 
+    /** Timeout for a mediaProcessing FGS, in milliseconds. */
+    private static final String KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION =
+            "media_processing_fgs_timeout_duration";
+
+    /** @see #KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION */
+    static final long DEFAULT_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION = 6 * 60 * 60_000; // 6 hours
+
+    /** @see #KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION */
+    public volatile long mMediaProcessingFgsTimeoutDuration =
+            DEFAULT_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION;
+
+    /** Timeout for a dataSync FGS, in milliseconds. */
+    private static final String KEY_DATA_SYNC_FGS_TIMEOUT_DURATION =
+            "data_sync_fgs_timeout_duration";
+
+    /** @see #KEY_DATA_SYNC_FGS_TIMEOUT_DURATION */
+    static final long DEFAULT_DATA_SYNC_FGS_TIMEOUT_DURATION = 6 * 60 * 60_000; // 6 hours
+
+    /** @see #KEY_DATA_SYNC_FGS_TIMEOUT_DURATION */
+    public volatile long mDataSyncFgsTimeoutDuration = DEFAULT_DATA_SYNC_FGS_TIMEOUT_DURATION;
+
     /**
      * If enabled, when starting an application, the system will wait for a
      * {@link ActivityManagerService#finishAttachApplication} from the app before scheduling
@@ -1082,6 +1103,20 @@
     public volatile long mShortFgsAnrExtraWaitDuration =
             DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION;
 
+    /**
+     * If a service of a timeout-enforced type doesn't finish within this duration after its
+     * timeout, then we'll declare an ANR.
+     * i.e. if the time limit for a type is 1 hour, and this extra duration is 10 seconds, then
+     * the app will be ANR'ed 1 hour and 10 seconds after it started.
+     */
+    private static final String KEY_FGS_ANR_EXTRA_WAIT_DURATION = "fgs_anr_extra_wait_duration";
+
+    /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */
+    static final long DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION = 10_000;
+
+    /** @see #KEY_FGS_ANR_EXTRA_WAIT_DURATION */
+    public volatile long mFgsAnrExtraWaitDuration = DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION;
+
     /** @see #KEY_USE_TIERED_CACHED_ADJ */
     public boolean USE_TIERED_CACHED_ADJ = DEFAULT_USE_TIERED_CACHED_ADJ;
 
@@ -1264,9 +1299,18 @@
                             case KEY_SHORT_FGS_PROC_STATE_EXTRA_WAIT_DURATION:
                                 updateShortFgsProcStateExtraWaitDuration();
                                 break;
+                            case KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION:
+                                updateMediaProcessingFgsTimeoutDuration();
+                                break;
+                            case KEY_DATA_SYNC_FGS_TIMEOUT_DURATION:
+                                updateDataSyncFgsTimeoutDuration();
+                                break;
                             case KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION:
                                 updateShortFgsAnrExtraWaitDuration();
                                 break;
+                            case KEY_FGS_ANR_EXTRA_WAIT_DURATION:
+                                updateFgsAnrExtraWaitDuration();
+                                break;
                             case KEY_PROACTIVE_KILLS_ENABLED:
                                 updateProactiveKillsEnabled();
                                 break;
@@ -2064,6 +2108,27 @@
                 DEFAULT_SHORT_FGS_ANR_EXTRA_WAIT_DURATION);
     }
 
+    private void updateMediaProcessingFgsTimeoutDuration() {
+        mMediaProcessingFgsTimeoutDuration = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION,
+                DEFAULT_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION);
+    }
+
+    private void updateDataSyncFgsTimeoutDuration() {
+        mDataSyncFgsTimeoutDuration = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_DATA_SYNC_FGS_TIMEOUT_DURATION,
+                DEFAULT_DATA_SYNC_FGS_TIMEOUT_DURATION);
+    }
+
+    private void updateFgsAnrExtraWaitDuration() {
+        mFgsAnrExtraWaitDuration = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_FGS_ANR_EXTRA_WAIT_DURATION,
+                DEFAULT_FGS_ANR_EXTRA_WAIT_DURATION);
+    }
+
     private void updateEnableWaitForFinishAttachApplication() {
         mEnableWaitForFinishAttachApplication = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -2295,6 +2360,13 @@
         pw.print("  "); pw.print(KEY_SHORT_FGS_ANR_EXTRA_WAIT_DURATION);
         pw.print("="); pw.println(mShortFgsAnrExtraWaitDuration);
 
+        pw.print("  "); pw.print(KEY_MEDIA_PROCESSING_FGS_TIMEOUT_DURATION);
+        pw.print("="); pw.println(mMediaProcessingFgsTimeoutDuration);
+        pw.print("  "); pw.print(KEY_DATA_SYNC_FGS_TIMEOUT_DURATION);
+        pw.print("="); pw.println(mDataSyncFgsTimeoutDuration);
+        pw.print("  "); pw.print(KEY_FGS_ANR_EXTRA_WAIT_DURATION);
+        pw.print("="); pw.println(mFgsAnrExtraWaitDuration);
+
         pw.print("  "); pw.print(KEY_USE_TIERED_CACHED_ADJ);
         pw.print("="); pw.println(USE_TIERED_CACHED_ADJ);
         pw.print("  "); pw.print(KEY_TIERED_CACHED_ADJ_DECAY_TIME);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2750344..bfdcb95 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1702,6 +1702,8 @@
     static final int REMOVE_UID_FROM_OBSERVER_MSG = 81;
     static final int BIND_APPLICATION_TIMEOUT_SOFT_MSG = 82;
     static final int BIND_APPLICATION_TIMEOUT_HARD_MSG = 83;
+    static final int SERVICE_FGS_TIMEOUT_MSG = 84;
+    static final int SERVICE_FGS_ANR_TIMEOUT_MSG = 85;
 
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
 
@@ -2064,6 +2066,12 @@
                 case BIND_APPLICATION_TIMEOUT_HARD_MSG: {
                     handleBindApplicationTimeoutHard((ProcessRecord) msg.obj);
                 } break;
+                case SERVICE_FGS_TIMEOUT_MSG: {
+                    mServices.onFgsTimeout((ServiceRecord) msg.obj);
+                } break;
+                case SERVICE_FGS_ANR_TIMEOUT_MSG: {
+                    mServices.onFgsAnrTimeout((ServiceRecord) msg.obj);
+                } break;
             }
         }
     }
@@ -13794,6 +13802,13 @@
     }
 
     @Override
+    public boolean hasServiceTimeLimitExceeded(ComponentName className, IBinder token) {
+        synchronized (this) {
+            return mServices.hasServiceTimedOutLocked(className, token);
+        }
+    }
+
+    @Override
     public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
             boolean requireFull, String name, String callerPackage) {
         return mUserController.handleIncomingUser(callingPid, callingUid, userId, allowAll,
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 2771572..3c8d7fc 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -56,6 +56,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.ArrayMap;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
@@ -236,6 +237,8 @@
     boolean mFgsNotificationShown;
     // Whether FGS package has permissions to show notifications.
     boolean mFgsHasNotificationPermission;
+    // Whether the FGS contains a type that is time limited.
+    private boolean mFgsIsTimeLimited;
 
     // allow the service becomes foreground service? Service started from background may not be
     // allowed to become a foreground service.
@@ -915,6 +918,7 @@
             pw.print(prefix); pw.print("isForeground="); pw.print(isForeground);
             pw.print(" foregroundId="); pw.print(foregroundId);
             pw.printf(" types=%08X", foregroundServiceType);
+            pw.print(" fgsHasTimeLimitedType="); pw.print(mFgsIsTimeLimited);
             pw.print(" foregroundNoti="); pw.println(foregroundNoti);
 
             if (isShortFgs() && mShortFgsInfo != null) {
@@ -1789,6 +1793,83 @@
                 + " " + (mShortFgsInfo == null ? "" : mShortFgsInfo.getDescription());
     }
 
+    /**
+     * @return true if one of the types of this FGS has a time limit.
+     */
+    public boolean isFgsTimeLimited() {
+        return startRequested && isForeground && canFgsTypeTimeOut(foregroundServiceType);
+    }
+
+    /**
+     * Called when a FGS with a time-limited type starts ({@code true}) or stops ({@code false}).
+     */
+    public void setIsFgsTimeLimited(boolean fgsIsTimeLimited) {
+        this.mFgsIsTimeLimited = fgsIsTimeLimited;
+    }
+
+    /**
+     * @return whether {@link #mFgsIsTimeLimited} was previously set or not.
+     */
+    public boolean wasFgsPreviouslyTimeLimited() {
+        return mFgsIsTimeLimited;
+    }
+
+    /**
+     * @return the FGS type if the service has reached its time limit, otherwise -1.
+     */
+    public int getTimedOutFgsType(long nowUptime) {
+        if (!isAppAlive() || !isFgsTimeLimited()) {
+            return -1;
+        }
+
+        final Pair<Integer, Long> fgsTypeAndStopTime = getEarliestStopTypeAndTime();
+        if (fgsTypeAndStopTime.first != -1 && fgsTypeAndStopTime.second <= nowUptime) {
+            return fgsTypeAndStopTime.first;
+        }
+        return -1; // no fgs type exceeded time limit
+    }
+
+    /**
+     * @return a {@code Pair<fgs_type, stop_time>}, representing the earliest time at which the FGS
+     * should be stopped (fgs start time + time limit for most restrictive type)
+     */
+    Pair<Integer, Long> getEarliestStopTypeAndTime() {
+        int fgsType = -1;
+        long timeout = 0;
+        if ((foregroundServiceType & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING)
+                == ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING) {
+            fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING;
+            timeout = ams.mConstants.mMediaProcessingFgsTimeoutDuration;
+        }
+        if ((foregroundServiceType & ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
+                == ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) {
+            // update the timeout and type if this type has a more restrictive time limit
+            if (timeout == 0 || ams.mConstants.mDataSyncFgsTimeoutDuration < timeout) {
+                fgsType = ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC;
+                timeout = ams.mConstants.mDataSyncFgsTimeoutDuration;
+            }
+        }
+        // Add the logic for time limits introduced in the future for other fgs types here.
+        return Pair.create(fgsType, timeout == 0 ? 0 : (mFgsEnterTime + timeout));
+    }
+
+    /**
+     * Check if the given types contain a type which is time restricted.
+     */
+    boolean canFgsTypeTimeOut(int fgsType) {
+        // The below conditionals are not simplified on purpose to help with readability.
+        if ((fgsType & ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING)
+                == ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING) {
+            return true;
+        }
+        if ((fgsType & ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
+                == ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) {
+            return true;
+        }
+        // Additional types which have time limits should be added here in the future.
+        return false;
+    }
+
     private boolean isAppAlive() {
         if (app == null) {
             return false;
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index bd3c8e0..feab2c05 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -157,7 +157,6 @@
       ]
     },
     {
-      "file_patterns": ["Broadcast.*"],
       "name": "CtsContentTestCases",
       "options": [
         { "include-filter": "android.content.cts.BroadcastReceiverTest" },
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 96c6be8..55ac4cf 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1350,41 +1350,57 @@
     }
 
     /**
-     * For mDelayUserDataLocking mode, storage once unlocked is kept unlocked.
-     * Total number of unlocked user storage is limited by mMaxRunningUsers.
-     * If there are more unlocked users, evict and lock the least recently stopped user and
-     * lock that user's data. Regardless of the mode, ephemeral user is always locked
-     * immediately.
+     * Returns which user, if any, should be locked when the given user is stopped.
+     *
+     * For typical (non-mDelayUserDataLocking) devices and users, this will be the provided user.
+     *
+     * However, for some devices or users (based on {@link #canDelayDataLockingForUser(int)}),
+     * storage once unlocked is kept unlocked, even after the user is stopped, so the user to be
+     * locked (if any) may differ.
+     *
+     * For mDelayUserDataLocking devices, the total number of unlocked user storage is limited
+     * (currently by mMaxRunningUsers). If there are more unlocked users, evict and lock the least
+     * recently stopped user and lock that user's data.
+     *
+     * Regardless of the mode, ephemeral user is always locked immediately.
      *
      * @return user id to lock. UserHandler.USER_NULL will be returned if no user should be locked.
      */
     @GuardedBy("mLock")
     private int updateUserToLockLU(@UserIdInt int userId, boolean allowDelayedLocking) {
-        int userIdToLock = userId;
-        // TODO: Decouple the delayed locking flows from mMaxRunningUsers or rename the property to
-        // state maximum running unlocked users specifically
-        if (canDelayDataLockingForUser(userIdToLock) && allowDelayedLocking
-                && !getUserInfo(userId).isEphemeral()
-                && !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
+        if (!canDelayDataLockingForUser(userId)
+                || !allowDelayedLocking
+                || getUserInfo(userId).isEphemeral()
+                || hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
+            return userId;
+        }
+
+        // Once we reach here, we are in a delayed locking scenario.
+        // Now, no user will be locked, unless the device's policy dictates we should based on the
+        // maximum of such users allowed for the device.
+        if (mDelayUserDataLocking) {
             // arg should be object, not index
             mLastActiveUsersForDelayedLocking.remove((Integer) userId);
             mLastActiveUsersForDelayedLocking.add(0, userId);
             int totalUnlockedUsers = mStartedUsers.size()
                     + mLastActiveUsersForDelayedLocking.size();
+            // TODO: Decouple the delayed locking flows from mMaxRunningUsers. These users aren't
+            //  running so this calculation shouldn't be based on this parameter. Also note that
+            //  that if these devices ever support background running users (such as profiles), the
+            //  implementation is incorrect since starting such users can cause the max to be
+            //  exceeded.
             if (totalUnlockedUsers > mMaxRunningUsers) { // should lock a user
-                userIdToLock = mLastActiveUsersForDelayedLocking.get(
+                final int userIdToLock = mLastActiveUsersForDelayedLocking.get(
                         mLastActiveUsersForDelayedLocking.size() - 1);
                 mLastActiveUsersForDelayedLocking
                         .remove(mLastActiveUsersForDelayedLocking.size() - 1);
-                Slogf.i(TAG, "finishUserStopped, stopping user:" + userId
-                        + " lock user:" + userIdToLock);
-            } else {
-                Slogf.i(TAG, "finishUserStopped, user:" + userId + ", skip locking");
-                // do not lock
-                userIdToLock = UserHandle.USER_NULL;
+                Slogf.i(TAG, "finishUserStopped: should stop user " + userId
+                        + " but should lock user " + userIdToLock);
+                return userIdToLock;
             }
         }
-        return userIdToLock;
+        Slogf.i(TAG, "finishUserStopped: should stop user " + userId + " but without any locking");
+        return UserHandle.USER_NULL;
     }
 
     /**
diff --git a/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java b/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java
index a6d5050..9ed3a99 100644
--- a/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java
+++ b/services/core/java/com/android/server/appop/LegacyAppOpStateParser.java
@@ -49,15 +49,7 @@
      */
     public int readState(AtomicFile file, SparseArray<SparseIntArray> uidModes,
             SparseArray<ArrayMap<String, SparseIntArray>> userPackageModes) {
-        FileInputStream stream;
-        try {
-            stream = file.openRead();
-        } catch (FileNotFoundException e) {
-            Slog.i(TAG, "No existing app ops " + file.getBaseFile() + "; starting empty");
-            return NO_FILE_VERSION;
-        }
-
-        try {
+        try (FileInputStream stream = file.openRead()) {
             TypedXmlPullParser parser = Xml.resolvePullParser(stream);
             int type;
             while ((type = parser.next()) != XmlPullParser.START_TAG
@@ -95,6 +87,9 @@
                 }
             }
             return versionAtBoot;
+        } catch (FileNotFoundException e) {
+            Slog.i(TAG, "No existing app ops " + file.getBaseFile() + "; starting empty");
+            return NO_FILE_VERSION;
         } catch (XmlPullParserException e) {
             throw new RuntimeException(e);
         } catch (IOException e) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 53255a0..c59f4f7 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -32,6 +32,7 @@
 import static android.media.AudioManager.STREAM_SYSTEM;
 import static android.media.audio.Flags.autoPublicVolumeApiHardening;
 import static android.media.audio.Flags.automaticBtDeviceType;
+import static android.media.audio.Flags.featureSpatialAudioHeadtrackingLowLatency;
 import static android.media.audio.Flags.focusFreezeTestApi;
 import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
 import static android.os.Process.FIRST_APPLICATION_UID;
@@ -4519,6 +4520,10 @@
         pw.println("\nFun with Flags: ");
         pw.println("\tandroid.media.audio.autoPublicVolumeApiHardening:"
                 + autoPublicVolumeApiHardening());
+        pw.println("\tandroid.media.audio.Flags.automaticBtDeviceType:"
+                + automaticBtDeviceType());
+        pw.println("\tandroid.media.audio.featureSpatialAudioHeadtrackingLowLatency:"
+                + featureSpatialAudioHeadtrackingLowLatency());
         pw.println("\tandroid.media.audio.focusFreezeTestApi:"
                 + focusFreezeTestApi());
         pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:"
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 8d76731..3b5fa7f 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -26,6 +26,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
 import android.media.AudioAttributes;
@@ -1611,6 +1612,9 @@
         pw.println("\tsupports binaural:" + mBinauralSupported + " / transaural:"
                 + mTransauralSupported);
         pw.println("\tmSpatOutput:" + mSpatOutput);
+        pw.println("\thas FEATURE_AUDIO_SPATIAL_HEADTRACKING_LOW_LATENCY:"
+                + mAudioService.mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUDIO_SPATIAL_HEADTRACKING_LOW_LATENCY));
     }
 
     private static String spatStateString(int state) {
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index cb15abc..cd064ae 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -19,11 +19,11 @@
 import static android.Manifest.permission.CONTROL_DEVICE_STATE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
 import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
 import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
 
-import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
 import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_BASE_STATE;
 import static com.android.server.devicestate.OverrideRequest.OVERRIDE_REQUEST_TYPE_EMULATED_STATE;
 import static com.android.server.devicestate.OverrideRequestController.FLAG_POWER_SAVE_ENABLED;
@@ -40,6 +40,7 @@
 import android.app.ActivityManagerInternal;
 import android.app.IProcessObserver;
 import android.content.Context;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateInfo;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateManagerInternal;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
index 8c6068d..02c9bb3 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerShellCommand.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.os.Binder;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index d5945f4..65b393a 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -21,6 +21,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.IntRange;
+import android.hardware.devicestate.DeviceState;
 import android.util.Dumpable;
 
 import java.lang.annotation.Retention;
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java
index 20485c1..d92629f 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequest.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.os.IBinder;
 
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index f5f2fa8..6c3fd83d 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.os.IBinder;
 import android.util.Slog;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index c8c0482..a100fe0 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -77,6 +77,7 @@
     @GuardedBy("ImfLock.class") private int mCurSeq;
     @GuardedBy("ImfLock.class") private boolean mVisibleBound;
     @GuardedBy("ImfLock.class") private boolean mSupportsStylusHw;
+    @GuardedBy("ImfLock.class") private boolean mSupportsConnectionlessStylusHw;
 
     @Nullable private CountDownLatch mLatchForTesting;
 
@@ -243,10 +244,17 @@
     /**
      * Returns {@code true} if current IME supports Stylus Handwriting.
      */
+    @GuardedBy("ImfLock.class")
     boolean supportsStylusHandwriting() {
         return mSupportsStylusHw;
     }
 
+    /** Returns whether the current IME supports connectionless stylus handwriting sessions. */
+    @GuardedBy("ImfLock.class")
+    boolean supportsConnectionlessStylusHandwriting() {
+        return mSupportsConnectionlessStylusHw;
+    }
+
     /**
      * Used to bring IME service up to visible adjustment while it is being shown.
      */
@@ -298,6 +306,15 @@
                     if (supportsStylusHwChanged) {
                         InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
                     }
+                    boolean supportsConnectionlessStylusHwChanged =
+                            mSupportsConnectionlessStylusHw
+                                    != info.supportsConnectionlessStylusHandwriting();
+                    if (supportsConnectionlessStylusHwChanged) {
+                        mSupportsConnectionlessStylusHw =
+                                info.supportsConnectionlessStylusHandwriting();
+                        InputMethodManager
+                                .invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
+                    }
                     mService.initializeImeLocked(mCurMethod, mCurToken);
                     mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
                     mService.reRequestCurrentClientSessionLocked();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index b8a63cd..8f8993b 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1980,7 +1980,8 @@
     }
 
     @Override
-    public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
+    public boolean isStylusHandwritingAvailableAsUser(
+            @UserIdInt int userId, boolean connectionless) {
         if (UserHandle.getCallingUserId() != userId) {
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
@@ -1993,14 +1994,17 @@
 
             // Check if selected IME of current user supports handwriting.
             if (userId == mSettings.getUserId()) {
-                return mBindingController.supportsStylusHandwriting();
+                return mBindingController.supportsStylusHandwriting()
+                        && (!connectionless
+                                || mBindingController.supportsConnectionlessStylusHandwriting());
             }
             //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList.
             //TODO(b/210039666): use cache.
             final InputMethodSettings settings = queryMethodMapForUser(userId);
             final InputMethodInfo imi = settings.getMethodMap().get(
                     settings.getSelectedInputMethod());
-            return imi != null && imi.supportsStylusHandwriting();
+            return imi != null && imi.supportsStylusHandwriting()
+                    && (!connectionless || imi.supportsConnectionlessStylusHandwriting());
         }
     }
 
@@ -3560,8 +3564,7 @@
             ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
             mCurStatsToken = null;
 
-            if (!Flags.useHandwritingListenerForTooltype()
-                    && lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
+            if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
                 curMethod.updateEditorToolType(lastClickToolType);
             }
             mVisibilityApplier.performShowIme(windowToken, statsToken,
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index d0c0543..f645eaa 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -16,6 +16,7 @@
 
 package com.android.server.notification;
 
+import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
 import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT;
 import static android.content.Context.BIND_AUTO_CREATE;
 import static android.content.Context.BIND_FOREGROUND_SERVICE;
@@ -24,6 +25,7 @@
 import static android.os.UserHandle.USER_SYSTEM;
 import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -1802,6 +1804,8 @@
         public ComponentName component;
         public int userid;
         public boolean isSystem;
+        @FlaggedApi(FLAG_LIFETIME_EXTENSION_REFACTOR)
+        public boolean isSystemUi;
         public ServiceConnection connection;
         public int targetSdkVersion;
         public Pair<ComponentName, Integer> mKey;
@@ -1836,6 +1840,11 @@
             return isSystem;
         }
 
+        @FlaggedApi(FLAG_LIFETIME_EXTENSION_REFACTOR)
+        public boolean isSystemUi() {
+            return isSystemUi;
+        }
+
         @Override
         public String toString() {
             return new StringBuilder("ManagedServiceInfo[")
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e7ad99a..6936ae0 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -71,6 +71,7 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
+import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
 import static android.app.Flags.lifetimeExtensionRefactor;
 import static android.app.NotificationManager.zenModeFromInterruptionFilter;
 import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
@@ -159,6 +160,7 @@
 import android.Manifest.permission;
 import android.annotation.DurationMillisLong;
 import android.annotation.ElapsedRealtimeLong;
+import android.annotation.FlaggedApi;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1852,6 +1854,7 @@
             }
             if (ACTION_NOTIFICATION_TIMEOUT.equals(action)) {
                 final NotificationRecord record;
+                // TODO: b/323013410 - Record should be cloned instead of used directly.
                 synchronized (mNotificationLock) {
                     record = findNotificationByKeyLocked(intent.getStringExtra(EXTRA_KEY));
                 }
@@ -1864,6 +1867,14 @@
                                 FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB
                                         | FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY,
                                 true, record.getUserId(), REASON_TIMEOUT, null);
+                        // If cancellation will be prevented due to lifetime extension, we send an
+                        // update to system UI.
+                        synchronized (mNotificationLock) {
+                            maybeNotifySystemUiListenerLifetimeExtendedLocked(record,
+                                    record.getSbn().getPackageName(),
+                                    mActivityManager.getPackageImportance(
+                                            record.getSbn().getPackageName()));
+                        }
                     } else {
                         cancelNotification(record.getSbn().getUid(),
                                 record.getSbn().getInitialPid(),
@@ -3825,7 +3836,17 @@
             int mustNotHaveFlags = isCallingUidSystem() ? 0 :
                     (FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB | FLAG_AUTOGROUP_SUMMARY);
             if (lifetimeExtensionRefactor()) {
+                // Also don't allow client apps to cancel lifetime extended notifs.
                 mustNotHaveFlags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
+                // If cancellation will be prevented due to lifetime extension, we send an update to
+                // system UI.
+                NotificationRecord record = null;
+                final int packageImportance = mActivityManager.getPackageImportance(pkg);
+                synchronized (mNotificationLock) {
+                    record = findNotificationLocked(pkg, tag, id, userId);
+                    maybeNotifySystemUiListenerLifetimeExtendedLocked(record, pkg,
+                            packageImportance);
+                }
             }
 
             cancelNotificationInternal(pkg, opPkg, Binder.getCallingUid(), Binder.getCallingPid(),
@@ -3845,6 +3866,16 @@
                         pkg, null, 0, FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB
                                 | FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY,
                         userId, REASON_APP_CANCEL_ALL);
+                // If cancellation will be prevented due to lifetime extension, we send updates
+                // to system UI.
+                // In this case, we need to hold the lock to access these lists.
+                final int packageImportance = mActivityManager.getPackageImportance(pkg);
+                synchronized (mNotificationLock) {
+                    notifySystemUiListenerLifetimeExtendedListLocked(mNotificationList,
+                            packageImportance);
+                    notifySystemUiListenerLifetimeExtendedListLocked(mEnqueuedNotifications,
+                            packageImportance);
+                }
             } else {
                 cancelAllNotificationsInt(Binder.getCallingUid(), Binder.getCallingPid(),
                         pkg, null, 0, FLAG_FOREGROUND_SERVICE | FLAG_USER_INITIATED_JOB,
@@ -4891,11 +4922,19 @@
             final long identity = Binder.clearCallingIdentity();
             boolean notificationsRapidlyCleared = false;
             final String pkg;
+            final int packageImportance;
+            final ManagedServiceInfo info;
             try {
                 synchronized (mNotificationLock) {
-                    final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+                    info = mListeners.checkServiceTokenLocked(token);
                     pkg = info.component.getPackageName();
-
+                }
+                if (lifetimeExtensionRefactor()) {
+                    packageImportance = mActivityManager.getPackageImportance(pkg);
+                } else {
+                    packageImportance = IMPORTANCE_NONE;
+                }
+                synchronized (mNotificationLock) {
                     // Cancellation reason. If the token comes from assistant, label the
                     // cancellation as coming from the assistant; default to LISTENER_CANCEL.
                     int reason = REASON_LISTENER_CANCEL;
@@ -4917,7 +4956,7 @@
                                     || isNotificationRecent(r.getUpdateTimeMs());
                             cancelNotificationFromListenerLocked(info, callingUid, callingPid,
                                     r.getSbn().getPackageName(), r.getSbn().getTag(),
-                                    r.getSbn().getId(), userId, reason);
+                                    r.getSbn().getId(), userId, reason, packageImportance);
                         }
                     } else {
                         for (NotificationRecord notificationRecord : mNotificationList) {
@@ -4931,6 +4970,12 @@
                                     REASON_LISTENER_CANCEL_ALL, info, info.supportsProfiles(),
                                     FLAG_ONGOING_EVENT | FLAG_NO_CLEAR
                                             | FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+                            // If cancellation will be prevented due to lifetime extension, we send
+                            // an update to system UI.
+                            notifySystemUiListenerLifetimeExtendedListLocked(mNotificationList,
+                                    packageImportance);
+                            notifySystemUiListenerLifetimeExtendedListLocked(mEnqueuedNotifications,
+                                    packageImportance);
                         } else {
                             cancelAllLocked(callingUid, callingPid, info.userid,
                                     REASON_LISTENER_CANCEL_ALL, info, info.supportsProfiles(),
@@ -5051,10 +5096,14 @@
         @GuardedBy("mNotificationLock")
         private void cancelNotificationFromListenerLocked(ManagedServiceInfo info,
                 int callingUid, int callingPid, String pkg, String tag, int id, int userId,
-                int reason) {
+                int reason, int packageImportance) {
             int mustNotHaveFlags = FLAG_ONGOING_EVENT;
             if (lifetimeExtensionRefactor()) {
                 mustNotHaveFlags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
+                // If cancellation will be prevented due to lifetime extension, we send an update
+                // to system UI.
+                NotificationRecord record = findNotificationLocked(pkg, tag, id, userId);
+                maybeNotifySystemUiListenerLifetimeExtendedLocked(record, pkg, packageImportance);
             }
             cancelNotification(callingUid, callingPid, pkg, tag, id, 0 /* mustHaveFlags */,
                     mustNotHaveFlags,
@@ -5197,7 +5246,13 @@
             final int callingUid = Binder.getCallingUid();
             final int callingPid = Binder.getCallingPid();
             final long identity = Binder.clearCallingIdentity();
+            final int packageImportance;
             try {
+                if (lifetimeExtensionRefactor()) {
+                    packageImportance = mActivityManager.getPackageImportance(pkg);
+                } else {
+                    packageImportance = IMPORTANCE_NONE;
+                }
                 synchronized (mNotificationLock) {
                     final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
                     int cancelReason = REASON_LISTENER_CANCEL;
@@ -5210,7 +5265,7 @@
                                 + " use cancelNotification(key) instead.");
                     } else {
                         cancelNotificationFromListenerLocked(info, callingUid, callingPid,
-                                pkg, tag, id, info.userid, cancelReason);
+                                pkg, tag, id, info.userid, cancelReason, packageImportance);
                     }
                 }
             } finally {
@@ -6254,6 +6309,10 @@
             Objects.requireNonNull(user);
 
             verifyPrivilegedListener(token, user, false);
+
+            final NotificationChannel originalChannel = mPreferencesHelper.getNotificationChannel(
+                    pkg, getUidForPackageAndUser(pkg, user), channel.getId(), true);
+            verifyPrivilegedListenerUriPermission(Binder.getCallingUid(), channel, originalChannel);
             updateNotificationChannelInt(pkg, getUidForPackageAndUser(pkg, user), channel, true);
         }
 
@@ -6351,6 +6410,24 @@
             }
         }
 
+        private void verifyPrivilegedListenerUriPermission(int sourceUid,
+                @NonNull NotificationChannel updateChannel,
+                @Nullable NotificationChannel originalChannel) {
+            // Check that the NLS has the required permissions to access the channel
+            final Uri soundUri = updateChannel.getSound();
+            final Uri originalSoundUri =
+                    (originalChannel != null) ? originalChannel.getSound() : null;
+            if (soundUri != null && !Objects.equals(originalSoundUri, soundUri)) {
+                Binder.withCleanCallingIdentity(() -> {
+                    mUgmInternal.checkGrantUriPermission(sourceUid, null,
+                            ContentProvider.getUriWithoutUserId(soundUri),
+                            Intent.FLAG_GRANT_READ_URI_PERMISSION,
+                            ContentProvider.getUserIdFromUri(soundUri,
+                            UserHandle.getUserId(sourceUid)));
+                });
+            }
+        }
+
         private int getUidForPackageAndUser(String pkg, UserHandle user) throws RemoteException {
             int uid = INVALID_UID;
             final long identity = Binder.clearCallingIdentity();
@@ -11654,6 +11731,30 @@
         });
     }
 
+    @FlaggedApi(FLAG_LIFETIME_EXTENSION_REFACTOR)
+    @GuardedBy("mNotificationLock")
+    private void notifySystemUiListenerLifetimeExtendedListLocked(
+            List<NotificationRecord> notificationList, int packageImportance) {
+        for (int i = notificationList.size() - 1; i >= 0; --i) {
+            NotificationRecord record = notificationList.get(i);
+            maybeNotifySystemUiListenerLifetimeExtendedLocked(record,
+                    record.getSbn().getPackageName(), packageImportance);
+        }
+    }
+
+    @FlaggedApi(FLAG_LIFETIME_EXTENSION_REFACTOR)
+    @GuardedBy("mNotificationLock")
+    private void maybeNotifySystemUiListenerLifetimeExtendedLocked(NotificationRecord record,
+            String pkg, int packageImportance) {
+        if (record != null && (record.getSbn().getNotification().flags
+                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
+            boolean isAppForeground = pkg != null && packageImportance == IMPORTANCE_FOREGROUND;
+            mHandler.post(new EnqueueNotificationRunnable(record.getUser().getIdentifier(),
+                    record, isAppForeground,
+                    mPostNotificationTrackerFactory.newTracker(null)));
+        }
+    }
+
     public class NotificationListeners extends ManagedServices {
         static final String TAG_ENABLED_NOTIFICATION_LISTENERS = "enabled_listeners";
         static final String TAG_REQUESTED_LISTENERS = "request_listeners";
@@ -11777,6 +11878,11 @@
 
         @Override
         public void onServiceAdded(ManagedServiceInfo info) {
+            if (lifetimeExtensionRefactor()) {
+                // Only System or System UI can call registerSystemService, so if the caller is not
+                // system, we know it's system UI.
+                info.isSystemUi = !isCallerSystemOrPhone();
+            }
             final INotificationListener listener = (INotificationListener) info.service;
             final NotificationRankingUpdate update;
             synchronized (mNotificationLock) {
@@ -12141,6 +12247,23 @@
                         continue;
                     }
 
+                    if (lifetimeExtensionRefactor()) {
+                        // Checks if this is a request to notify system UI about a notification that
+                        // has been lifetime extended.
+                        // (We only need to check old for the flag, because in both cancellation and
+                        // update cases, old should have the flag.)
+                        // If it is such a request, and this is system UI, we send the post request
+                        // only to System UI, and break as we don't need to continue checking other
+                        // Managed Services.
+                        if (info.isSystemUi() && old != null && old.getNotification() != null
+                                && (old.getNotification().flags
+                                & Notification.FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY) > 0) {
+                            final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
+                            listenerCalls.add(() -> notifyPosted(info, oldSbn, update));
+                            break;
+                        }
+                    }
+
                     // If we shouldn't notify all listeners, this means the hidden state of
                     // a notification was changed.  Don't notifyPosted listeners targeting >= P.
                     // Instead, those listeners will receive notifyRankingUpdate.
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 88d23ce..82c5733 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -28,6 +28,7 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeDiff;
+import android.util.LocalLog;
 import android.util.Log;
 import android.util.Slog;
 
@@ -37,26 +38,16 @@
 import java.util.List;
 
 public class ZenLog {
-    private static final String TAG = "ZenLog";
-    // the ZenLog is *very* verbose, so be careful about setting this to true
-    private static final boolean DEBUG = false;
 
     private static final int SIZE = Build.IS_DEBUGGABLE ? 200 : 100;
 
-    private static final long[] TIMES = new long[SIZE];
-    private static final int[] TYPES = new int[SIZE];
-    private static final String[] MSGS = new String[SIZE];
-
-    private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
+    private static final LocalLog STATE_CHANGES = new LocalLog(SIZE);
+    private static final LocalLog INTERCEPTION_EVENTS = new LocalLog(SIZE);
 
     private static final int TYPE_INTERCEPTED = 1;
-    private static final int TYPE_ALLOW_DISABLE = 2;
     private static final int TYPE_SET_RINGER_MODE_EXTERNAL = 3;
     private static final int TYPE_SET_RINGER_MODE_INTERNAL = 4;
-    private static final int TYPE_DOWNTIME = 5;
     private static final int TYPE_SET_ZEN_MODE = 6;
-    private static final int TYPE_UPDATE_ZEN_MODE = 7;
-    private static final int TYPE_EXIT_CONDITION = 8;
     private static final int TYPE_SUBSCRIBE = 9;
     private static final int TYPE_UNSUBSCRIBE = 10;
     private static final int TYPE_CONFIG = 11;
@@ -71,9 +62,6 @@
     private static final int TYPE_CHECK_REPEAT_CALLER = 20;
     private static final int TYPE_ALERT_ON_UPDATED_INTERCEPT = 21;
 
-    private static int sNext;
-    private static int sSize;
-
     public static void traceIntercepted(NotificationRecord record, String reason) {
         append(TYPE_INTERCEPTED, record.getKey() + "," + reason);
     }
@@ -104,10 +92,6 @@
                 ringerModeToString(ringerModeExternalOut));
     }
 
-    public static void traceDowntimeAutotrigger(String result) {
-        append(TYPE_DOWNTIME, result);
-    }
-
     public static void traceSetZenMode(int zenMode, String reason) {
         append(TYPE_SET_ZEN_MODE, zenModeToString(zenMode) + "," + reason);
     }
@@ -120,21 +104,12 @@
         append(TYPE_SET_CONSOLIDATED_ZEN_POLICY, policy.toString() + "," + reason);
     }
 
-    public static void traceUpdateZenMode(int fromMode, int toMode) {
-        append(TYPE_UPDATE_ZEN_MODE, zenModeToString(fromMode) + " -> " + zenModeToString(toMode));
-    }
-
-    public static void traceExitCondition(Condition c, ComponentName component, String reason) {
-        append(TYPE_EXIT_CONDITION, c + "," + componentToString(component) + "," + reason);
-    }
 
     public static void traceSetNotificationPolicy(String pkg, int targetSdk,
             NotificationManager.Policy policy) {
         String policyLog = "pkg=" + pkg + " targetSdk=" + targetSdk
                 + " NotificationPolicy=" + policy.toString();
         append(TYPE_SET_NOTIFICATION_POLICY, policyLog);
-        // TODO(b/180205791): remove when we can better surface apps that are changing policy
-        Log.d(TAG, "Zen Policy Changed: " + policyLog);
     }
 
     public static void traceSubscribe(Uri uri, IConditionProvider provider, RemoteException e) {
@@ -145,13 +120,14 @@
         append(TYPE_UNSUBSCRIBE, uri + "," + subscribeResult(provider, e));
     }
 
-    public static void traceConfig(String reason, ZenModeConfig oldConfig,
-            ZenModeConfig newConfig) {
+    public static void traceConfig(String reason, ComponentName triggeringComponent,
+            ZenModeConfig oldConfig, ZenModeConfig newConfig, int callingUid) {
         ZenModeDiff.ConfigDiff diff = new ZenModeDiff.ConfigDiff(oldConfig, newConfig);
         if (diff == null || !diff.hasDiff()) {
             append(TYPE_CONFIG, reason + " no changes");
         } else {
             append(TYPE_CONFIG, reason
+                    + " - " + triggeringComponent + " : " + callingUid
                     + ",\n" + (newConfig != null ? newConfig.toString() : null)
                     + ",\n" + diff);
         }
@@ -204,13 +180,9 @@
     private static String typeToString(int type) {
         switch (type) {
             case TYPE_INTERCEPTED: return "intercepted";
-            case TYPE_ALLOW_DISABLE: return "allow_disable";
             case TYPE_SET_RINGER_MODE_EXTERNAL: return "set_ringer_mode_external";
             case TYPE_SET_RINGER_MODE_INTERNAL: return "set_ringer_mode_internal";
-            case TYPE_DOWNTIME: return "downtime";
             case TYPE_SET_ZEN_MODE: return "set_zen_mode";
-            case TYPE_UPDATE_ZEN_MODE: return "update_zen_mode";
-            case TYPE_EXIT_CONDITION: return "exit_condition";
             case TYPE_SUBSCRIBE: return "subscribe";
             case TYPE_UNSUBSCRIBE: return "unsubscribe";
             case TYPE_CONFIG: return "config";
@@ -278,30 +250,27 @@
     }
 
     private static void append(int type, String msg) {
-        synchronized(MSGS) {
-            TIMES[sNext] = System.currentTimeMillis();
-            TYPES[sNext] = type;
-            MSGS[sNext] = msg;
-            sNext = (sNext + 1) % SIZE;
-            if (sSize < SIZE) {
-                sSize++;
+        if (type == TYPE_INTERCEPTED || type == TYPE_NOT_INTERCEPTED
+                || type == TYPE_CHECK_REPEAT_CALLER || type == TYPE_RECORD_CALLER
+                || type == TYPE_MATCHES_CALL_FILTER || type == TYPE_ALERT_ON_UPDATED_INTERCEPT) {
+            synchronized (INTERCEPTION_EVENTS) {
+                INTERCEPTION_EVENTS.log(typeToString(type) + ": " +msg);
+            }
+        } else {
+            synchronized (STATE_CHANGES) {
+                STATE_CHANGES.log(typeToString(type) + ": " +msg);
             }
         }
-        if (DEBUG) Slog.d(TAG, typeToString(type) + ": " + msg);
     }
 
     public static void dump(PrintWriter pw, String prefix) {
-        synchronized(MSGS) {
-            final int start = (sNext - sSize + SIZE) % SIZE;
-            for (int i = 0; i < sSize; i++) {
-                final int j = (start + i) % SIZE;
-                pw.print(prefix);
-                pw.print(FORMAT.format(new Date(TIMES[j])));
-                pw.print(' ');
-                pw.print(typeToString(TYPES[j]));
-                pw.print(": ");
-                pw.println(MSGS[j]);
-            }
+        synchronized (INTERCEPTION_EVENTS) {
+            pw.printf(prefix  + "Interception Events:\n");
+            INTERCEPTION_EVENTS.dump(prefix, pw);
+        }
+        synchronized (STATE_CHANGES) {
+            pw.printf(prefix  + "State Changes:\n");
+            STATE_CHANGES.dump(prefix, pw);
         }
     }
 }
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index aebd28a..1c20b2d 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1713,7 +1713,7 @@
                 mConfigs.put(config.user, config);
             }
             if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable());
-            ZenLog.traceConfig(reason, mConfig, config);
+            ZenLog.traceConfig(reason, triggeringComponent, mConfig, config, callingUid);
 
             // send some broadcasts
             Policy newPolicy = getNotificationPolicy(config);
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 25a39cc..86d05d9 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -257,7 +257,7 @@
     private boolean matchesActorSignature(@NonNull AndroidPackage targetPackage,
             @NonNull AndroidPackage overlayPackage, int userId) {
         String targetOverlayableName = overlayPackage.getOverlayTargetOverlayableName();
-        if (targetOverlayableName != null) {
+        if (targetOverlayableName != null && !mPackageManager.getNamedActors().isEmpty()) {
             try {
                 OverlayableInfo overlayableInfo = mPackageManager.getOverlayableForTarget(
                         targetPackage.getPackageName(), targetOverlayableName, userId);
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index b9464d9..8729522 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -362,7 +362,7 @@
                 defaultPackages.add(packageName);
             }
         }
-        return defaultPackages.toArray(new String[defaultPackages.size()]);
+        return defaultPackages.toArray(new String[0]);
     }
 
     private final class OverlayManagerPackageMonitor extends PackageMonitor {
@@ -1160,7 +1160,7 @@
         // state may lead to contradictions within OMS. Better then to lag
         // behind until all pending intents have been processed.
         private final ArrayMap<String, PackageStateUsers> mCache = new ArrayMap<>();
-        private final Set<Integer> mInitializedUsers = new ArraySet<>();
+        private final ArraySet<Integer> mInitializedUsers = new ArraySet<>();
 
         PackageManagerHelperImpl(Context context) {
             mContext = context;
@@ -1176,8 +1176,7 @@
          */
         @NonNull
         public ArrayMap<String, PackageState> initializeForUser(final int userId) {
-            if (!mInitializedUsers.contains(userId)) {
-                mInitializedUsers.add(userId);
+            if (mInitializedUsers.add(userId)) {
                 mPackageManagerInternal.forEachPackageState((packageState -> {
                     if (packageState.getPkg() != null
                             && packageState.getUserStateOrDefault(userId).isInstalled()) {
@@ -1545,8 +1544,7 @@
                 final OverlayPaths frameworkOverlays =
                         mImpl.getEnabledOverlayPaths("android", userId, false);
                 for (final String targetPackageName : targetPackageNames) {
-                    final OverlayPaths.Builder list = new OverlayPaths.Builder();
-                    list.addAll(frameworkOverlays);
+                    final var list = new OverlayPaths.Builder(frameworkOverlays);
                     if (!"android".equals(targetPackageName)) {
                         list.addAll(mImpl.getEnabledOverlayPaths(targetPackageName, userId, true));
                     }
@@ -1558,17 +1556,21 @@
             final HashSet<String> invalidPackages = new HashSet<>();
             pm.setEnabledOverlayPackages(userId, pendingChanges, updatedPackages, invalidPackages);
 
-            for (final String targetPackageName : targetPackageNames) {
-                if (DEBUG) {
-                    Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=["
-                            + pendingChanges.get(targetPackageName)
-                            + "] userId=" + userId);
-                }
+            if (DEBUG || !invalidPackages.isEmpty()) {
+                for (final String targetPackageName : targetPackageNames) {
+                    if (DEBUG) {
+                        Slog.d(TAG,
+                                "-> Updating overlay: target=" + targetPackageName + " overlays=["
+                                        + pendingChanges.get(targetPackageName)
+                                        + "] userId=" + userId);
+                    }
 
-                if (invalidPackages.contains(targetPackageName)) {
-                    Slog.e(TAG, TextUtils.formatSimple(
-                            "Failed to change enabled overlays for %s user %d", targetPackageName,
-                            userId));
+                    if (invalidPackages.contains(targetPackageName)) {
+                        Slog.e(TAG, TextUtils.formatSimple(
+                                "Failed to change enabled overlays for %s user %d",
+                                targetPackageName,
+                                userId));
+                    }
                 }
             }
             return new ArrayList<>(updatedPackages);
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 972c78d..c1b6ccc 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -772,24 +772,20 @@
 
     OverlayPaths getEnabledOverlayPaths(@NonNull final String targetPackageName,
             final int userId, boolean includeImmutableOverlays) {
-        final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName,
-                userId);
-        final OverlayPaths.Builder paths = new OverlayPaths.Builder();
-        final int n = overlays.size();
-        for (int i = 0; i < n; i++) {
-            final OverlayInfo oi = overlays.get(i);
+        final var paths = new OverlayPaths.Builder();
+        mSettings.forEachMatching(userId, null, targetPackageName, oi -> {
             if (!oi.isEnabled()) {
-                continue;
+                return;
             }
             if (!includeImmutableOverlays && !oi.isMutable) {
-                continue;
+                return;
             }
             if (oi.isFabricated()) {
                 paths.addNonApkPath(oi.baseCodePath);
             } else {
                 paths.addApkPath(oi.baseCodePath);
             }
-        }
+        });
         return paths.build();
     }
 
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index eae614a..b8b49f3e 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -47,6 +47,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
@@ -182,6 +183,23 @@
         return CollectionUtils.map(items, SettingsItem::getOverlayInfo);
     }
 
+    void forEachMatching(int userId, String overlayName, String targetPackageName,
+            @NonNull Consumer<OverlayInfo> consumer) {
+        for (int i = 0, n = mItems.size(); i < n; i++) {
+            final SettingsItem item = mItems.get(i);
+            if (item.getUserId() != userId) {
+                continue;
+            }
+            if (overlayName != null && !item.mOverlay.getPackageName().equals(overlayName)) {
+                continue;
+            }
+            if (targetPackageName != null && !item.mTargetPackageName.equals(targetPackageName)) {
+                continue;
+            }
+            consumer.accept(item.getOverlayInfo());
+        }
+    }
+
     ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
         final List<SettingsItem> items = selectWhereUser(userId);
 
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index 1dd7905..18ba2cf 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -503,21 +503,24 @@
     private void assertPackageStorageValid(@NonNull Computer snapshot, String volumeUuid,
             String packageName, int userId) throws PackageManagerException {
         final PackageStateInternal packageState = snapshot.getPackageStateInternal(packageName);
-        final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
         if (packageState == null) {
             throw PackageManagerException.ofInternalError("Package " + packageName + " is unknown",
                     PackageManagerException.INTERNAL_ERROR_STORAGE_INVALID_PACKAGE_UNKNOWN);
-        } else if (!TextUtils.equals(volumeUuid, packageState.getVolumeUuid())) {
+        }
+        if (!TextUtils.equals(volumeUuid, packageState.getVolumeUuid())) {
             throw PackageManagerException.ofInternalError(
                     "Package " + packageName + " found on unknown volume " + volumeUuid
                             + "; expected volume " + packageState.getVolumeUuid(),
                     PackageManagerException.INTERNAL_ERROR_STORAGE_INVALID_VOLUME_UNKNOWN);
-        } else if (!userState.isInstalled() && !userState.dataExists()) {
+        }
+        final PackageUserStateInternal userState = packageState.getUserStateOrDefault(userId);
+        if (!userState.isInstalled() && !userState.dataExists()) {
             throw PackageManagerException.ofInternalError(
                     "Package " + packageName + " not installed for user " + userId
                             + " or was deleted without DELETE_KEEP_DATA",
                     PackageManagerException.INTERNAL_ERROR_STORAGE_INVALID_NOT_INSTALLED_FOR_USER);
-        } else if (packageState.getPkg() != null
+        }
+        if (packageState.getPkg() != null
                 && !shouldHaveAppStorage(packageState.getPkg())) {
             throw PackageManagerException.ofInternalError(
                     "Package " + packageName + " shouldn't have storage",
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index afcf5a0..76952b3 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -29,6 +29,7 @@
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
 import android.os.Environment;
 import android.os.PowerManager;
 import android.util.ArrayMap;
@@ -40,7 +41,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
-import com.android.server.devicestate.DeviceState;
 import com.android.server.devicestate.DeviceStateProvider;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.policy.devicestate.config.Conditions;
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 00036e4..af4da81 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -842,7 +842,7 @@
     private int mBatteryChargeUah;
     private int mBatteryHealth;
     private int mBatteryTemperature;
-    private int mBatteryVoltageMv = -1;
+    private int mBatteryVoltageMv;
 
     @NonNull
     private final BatteryStatsHistory mHistory;
diff --git a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
index 0de73a5..b0dcf95 100644
--- a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
+++ b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
@@ -30,7 +30,9 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.selinux.RateLimiter;
 
+import java.time.Duration;
 import java.util.List;
 import java.util.Map;
 
@@ -131,19 +133,36 @@
 
     private final Handler mMobileDataStatsHandler;
 
+    private final RateLimiter mRateLimiter;
+
     AggregatedMobileDataStatsPuller(NetworkStatsManager networkStatsManager) {
+        if (DEBUG) {
+            if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+                Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
+                        TAG + "-AggregatedMobileDataStatsPullerInit");
+            }
+        }
+
+        mRateLimiter = new RateLimiter(/* window= */ Duration.ofSeconds(1));
+
         mUidStats = new ArrayMap<>();
         mUidPreviousState = new SparseIntArray();
 
         mNetworkStatsManager = networkStatsManager;
 
-        if (mNetworkStatsManager != null) {
-            updateNetworkStats(mNetworkStatsManager);
-        }
-
         HandlerThread mMobileDataStatsHandlerThread = new HandlerThread("MobileDataStatsHandler");
         mMobileDataStatsHandlerThread.start();
         mMobileDataStatsHandler = new Handler(mMobileDataStatsHandlerThread.getLooper());
+
+        if (mNetworkStatsManager != null) {
+            mMobileDataStatsHandler.post(
+                    () -> {
+                        updateNetworkStats(mNetworkStatsManager);
+                    });
+        }
+        if (DEBUG) {
+            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+        }
     }
 
     public void noteUidProcessState(int uid, int state, long unusedElapsedRealtime,
@@ -180,18 +199,20 @@
     }
 
     private void noteUidProcessStateImpl(int uid, int state) {
-        // noteUidProcessStateLocked can be called back to back several times while
-        // the updateNetworkStatsLocked loops over several stats for multiple uids
-        // and during the first call in a batch of proc state change event it can
-        // contain info for uid with unknown previous state yet which can happen due to a few
-        // reasons:
-        // - app was just started
-        // - app was started before the ActivityManagerService
-        // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN
-        if (mNetworkStatsManager != null) {
-            updateNetworkStats(mNetworkStatsManager);
-        } else {
-            Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager");
+        if (mRateLimiter.tryAcquire()) {
+            // noteUidProcessStateImpl can be called back to back several times while
+            // the updateNetworkStats loops over several stats for multiple uids
+            // and during the first call in a batch of proc state change event it can
+            // contain info for uid with unknown previous state yet which can happen due to a few
+            // reasons:
+            // - app was just started
+            // - app was started before the ActivityManagerService
+            // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN
+            if (mNetworkStatsManager != null) {
+                updateNetworkStats(mNetworkStatsManager);
+            } else {
+                Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager");
+            }
         }
         mUidPreviousState.put(uid, state);
     }
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 6a3cf43..a3e2869 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.view.View.DRAG_FLAG_GLOBAL;
+import static android.view.View.DRAG_FLAG_GLOBAL_SAME_APPLICATION;
+
 import static com.android.input.flags.Flags.enablePointerChoreographer;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
@@ -30,15 +33,20 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.RemoteException;
 import android.util.Slog;
 import android.view.Display;
+import android.view.DragEvent;
 import android.view.IWindow;
 import android.view.InputDevice;
 import android.view.PointerIcon;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
+import android.window.IUnhandledDragCallback;
+import android.window.IUnhandledDragListener;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
 
 import java.util.Objects;
@@ -59,6 +67,7 @@
     static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 1;
     static final int MSG_ANIMATION_END = 2;
     static final int MSG_REMOVE_DRAG_SURFACE_TIMEOUT = 3;
+    static final int MSG_UNHANDLED_DROP_LISTENER_TIMEOUT = 4;
 
     /**
      * Drag state per operation.
@@ -72,6 +81,21 @@
     private WindowManagerService mService;
     private final Handler mHandler;
 
+    // The unhandled drag listener for handling cross-window drags that end with no target window
+    private IUnhandledDragListener mUnhandledDragListener;
+    private final IBinder.DeathRecipient mUnhandledDragListenerDeathRecipient =
+            new IBinder.DeathRecipient() {
+        @Override
+        public void binderDied() {
+            synchronized (mService.mGlobalLock) {
+                if (hasPendingUnhandledDropCallback()) {
+                    onUnhandledDropCallback(false /* consumedByListeners */);
+                }
+                setUnhandledDragListener(null);
+            }
+        }
+    };
+
     /**
      * Callback which is used to sync drag state with the vendor-specific code.
      */
@@ -83,10 +107,16 @@
         mHandler = new DragHandler(service, looper);
     }
 
+    @VisibleForTesting
+    Handler getHandler() {
+        return mHandler;
+    }
+
     boolean dragDropActiveLocked() {
         return mDragState != null && !mDragState.isClosing();
     }
 
+    @VisibleForTesting
     boolean dragSurfaceRelinquishedToDropTarget() {
         return mDragState != null && mDragState.mRelinquishDragSurfaceToDropTarget;
     }
@@ -96,6 +126,32 @@
         mCallback.set(callback);
     }
 
+    /**
+     * Sets the listener for unhandled cross-window drags.
+     */
+    public void setUnhandledDragListener(IUnhandledDragListener listener) {
+        if (mUnhandledDragListener != null && mUnhandledDragListener.asBinder() != null) {
+            mUnhandledDragListener.asBinder().unlinkToDeath(
+                    mUnhandledDragListenerDeathRecipient, 0);
+        }
+        mUnhandledDragListener = listener;
+        if (listener != null && listener.asBinder() != null) {
+            try {
+                mUnhandledDragListener.asBinder().linkToDeath(
+                        mUnhandledDragListenerDeathRecipient, 0);
+            } catch (RemoteException e) {
+                mUnhandledDragListener = null;
+            }
+        }
+    }
+
+    /**
+     * Returns whether there is an unhandled drag listener set.
+     */
+    boolean hasUnhandledDragListener() {
+        return mUnhandledDragListener != null;
+    }
+
     void sendDragStartedIfNeededLocked(WindowState window) {
         mDragState.sendDragStartedIfNeededLocked(window);
     }
@@ -247,6 +303,10 @@
         }
     }
 
+    /**
+     * This is called from the drop target window that received ACTION_DROP
+     * (see DragState#reportDropWindowLock()).
+     */
     void reportDropResult(IWindow window, boolean consumed) {
         IBinder token = window.asBinder();
         if (DEBUG_DRAG) {
@@ -273,22 +333,89 @@
                 // so be sure to halt the timeout even if the later WindowState
                 // lookup fails.
                 mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder());
+
                 WindowState callingWin = mService.windowForClientLocked(null, window, false);
                 if (callingWin == null) {
                     Slog.w(TAG_WM, "Bad result-reporting window " + window);
                     return;  // !!! TODO: throw here?
                 }
 
-                mDragState.mDragResult = consumed;
-                mDragState.mRelinquishDragSurfaceToDropTarget = consumed
-                        && mDragState.targetInterceptsGlobalDrag(callingWin);
-                mDragState.endDragLocked();
+                // If the drop was not consumed by the target window, then check if it should be
+                // consumed by the system unhandled drag listener
+                if (!consumed && notifyUnhandledDrop(mDragState.mUnhandledDropEvent,
+                        "window-drop")) {
+                    // If the unhandled drag listener is notified, then defer ending the drag until
+                    // the listener calls back
+                    return;
+                }
+
+                final boolean relinquishDragSurfaceToDropTarget =
+                        consumed && mDragState.targetInterceptsGlobalDrag(callingWin);
+                mDragState.endDragLocked(consumed, relinquishDragSurfaceToDropTarget);
             }
         } finally {
             mCallback.get().postReportDropResult();
         }
     }
 
+    /**
+     * Notifies the unhandled drag listener if needed.
+     * @return whether the listener was notified and subsequent drag completion should be deferred
+     *         until the listener calls back
+     */
+    boolean notifyUnhandledDrop(DragEvent dropEvent, String reason) {
+        final boolean isLocalDrag =
+                (mDragState.mFlags & (DRAG_FLAG_GLOBAL_SAME_APPLICATION | DRAG_FLAG_GLOBAL)) == 0;
+        if (!com.android.window.flags.Flags.delegateUnhandledDrags()
+                || mUnhandledDragListener == null
+                || isLocalDrag) {
+            // Skip if the flag is disabled, there is no unhandled-drag listener, or if this is a
+            // purely local drag
+            if (DEBUG_DRAG) Slog.d(TAG_WM, "Skipping unhandled listener "
+                    + "(listener=" + mUnhandledDragListener + ", flags=" + mDragState.mFlags + ")");
+            return false;
+        }
+        if (DEBUG_DRAG) Slog.d(TAG_WM, "Sending DROP to unhandled listener (" + reason + ")");
+        try {
+            // Schedule timeout for the unhandled drag listener to call back
+            sendTimeoutMessage(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT, null, DRAG_TIMEOUT_MS);
+            mUnhandledDragListener.onUnhandledDrop(dropEvent, new IUnhandledDragCallback.Stub() {
+                @Override
+                public void notifyUnhandledDropComplete(boolean consumedByListener) {
+                    if (DEBUG_DRAG) Slog.d(TAG_WM, "Unhandled listener finished handling DROP");
+                    synchronized (mService.mGlobalLock) {
+                        onUnhandledDropCallback(consumedByListener);
+                    }
+                }
+            });
+            return true;
+        } catch (RemoteException e) {
+            Slog.e(TAG_WM, "Failed to call unhandled drag listener", e);
+            return false;
+        }
+    }
+
+    /**
+     * Called when the unhandled drag listener has completed handling the drop
+     * (if it was notififed).
+     */
+    @VisibleForTesting
+    void onUnhandledDropCallback(boolean consumedByListener) {
+        mHandler.removeMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT, null);
+        // If handled, then the listeners assume responsibility of cleaning up the drag surface
+        mDragState.mDragResult = consumedByListener;
+        mDragState.mRelinquishDragSurfaceToDropTarget = consumedByListener;
+        mDragState.closeLocked();
+    }
+
+    /**
+     * Returns whether we are currently waiting for the unhandled drag listener to callback after
+     * it was notified of an unhandled drag.
+     */
+    boolean hasPendingUnhandledDropCallback() {
+        return mHandler.hasMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT);
+    }
+
     void cancelDragAndDrop(IBinder dragToken, boolean skipAnimation) {
         if (DEBUG_DRAG) {
             Slog.d(TAG_WM, "cancelDragAndDrop");
@@ -436,8 +563,8 @@
                     synchronized (mService.mGlobalLock) {
                         // !!! TODO: ANR the drag-receiving app
                         if (mDragState != null) {
-                            mDragState.mDragResult = false;
-                            mDragState.endDragLocked();
+                            mDragState.endDragLocked(false /* consumed */,
+                                    false /* relinquishDragSurfaceToDropTarget */);
                         }
                     }
                     break;
@@ -473,6 +600,13 @@
                     }
                     break;
                 }
+
+                case MSG_UNHANDLED_DROP_LISTENER_TIMEOUT: {
+                    synchronized (mService.mGlobalLock) {
+                        onUnhandledDropCallback(false /* consumedByListener */);
+                    }
+                    break;
+                }
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index d302f06..76038b9 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -147,6 +147,11 @@
      */
     private boolean mIsClosing;
 
+    // Stores the last drop event which was reported to a valid drop target window, or null
+    // otherwise.  This drop event will contain private info and should only be consumed by the
+    // unhandled drag listener.
+    DragEvent mUnhandledDropEvent;
+
     DragState(WindowManagerService service, DragDropController controller, IBinder token,
             SurfaceControl surface, int flags, IBinder localWin) {
         mService = service;
@@ -287,14 +292,54 @@
         mData = null;
         mThumbOffsetX = mThumbOffsetY = 0;
         mNotifiedWindows = null;
+        if (mUnhandledDropEvent != null) {
+            mUnhandledDropEvent.recycle();
+            mUnhandledDropEvent = null;
+        }
 
         // Notifies the controller that the drag state is closed.
         mDragDropController.onDragStateClosedLocked(this);
     }
 
     /**
+     * Creates the drop event for this drag gesture.  If `touchedWin` is null, then the drop event
+     * will be created for dispatching to the unhandled drag and the drag surface will be provided
+     * as a part of the dispatched event.
+     */
+    private DragEvent createDropEvent(float x, float y, @Nullable WindowState touchedWin,
+            boolean includeDragSurface) {
+        if (touchedWin != null) {
+            final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
+            final DragAndDropPermissionsHandler dragAndDropPermissions;
+            if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0
+                    && mData != null) {
+                dragAndDropPermissions = new DragAndDropPermissionsHandler(mService.mGlobalLock,
+                        mData,
+                        mUid,
+                        touchedWin.getOwningPackage(),
+                        mFlags & DRAG_FLAGS_URI_PERMISSIONS,
+                        mSourceUserId,
+                        targetUserId);
+            } else {
+                dragAndDropPermissions = null;
+            }
+            if (mSourceUserId != targetUserId) {
+                if (mData != null) {
+                    mData.fixUris(mSourceUserId);
+                }
+            }
+            return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mData,
+                    targetInterceptsGlobalDrag(touchedWin), dragAndDropPermissions);
+        } else {
+            return obtainDragEvent(DragEvent.ACTION_DROP, x, y, mData,
+                    includeDragSurface /* includeDragSurface */, null /* dragAndDropPermissions */);
+        }
+    }
+
+    /**
      * Notify the drop target and tells it about the data. If the drop event is not sent to the
-     * target, invokes {@code endDragLocked} immediately.
+     * target, invokes {@code endDragLocked} after the unhandled drag listener gets a chance to
+     * handle the drop.
      */
     boolean reportDropWindowLock(IBinder token, float x, float y) {
         if (mAnimator != null) {
@@ -302,41 +347,27 @@
         }
 
         final WindowState touchedWin = mService.mInputToWindowMap.get(token);
+        final DragEvent unhandledDropEvent = createDropEvent(x, y, null /* touchedWin */,
+                true /* includePrivateInfo */);
         if (!isWindowNotified(touchedWin)) {
-            // "drop" outside a valid window -- no recipient to apply a
-            // timeout to, and we can send the drag-ended message immediately.
-            mDragResult = false;
-            endDragLocked();
+            // Delegate to the unhandled drag listener as a first pass
+            if (mDragDropController.notifyUnhandledDrop(unhandledDropEvent, "unhandled-drop")) {
+                // The unhandled drag listener will call back to notify whether it has consumed
+                // the drag, so return here
+                return true;
+            }
+
+            // "drop" outside a valid window -- no recipient to apply a timeout to, and we can send
+            // the drag-ended message immediately.
+            endDragLocked(false /* consumed */, false /* relinquishDragSurfaceToDropTarget */);
             if (DEBUG_DRAG) Slog.d(TAG_WM, "Drop outside a valid window " + touchedWin);
             return false;
         }
 
         if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin);
 
-        final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
-
-        final DragAndDropPermissionsHandler dragAndDropPermissions;
-        if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0
-                && mData != null) {
-            dragAndDropPermissions = new DragAndDropPermissionsHandler(mService.mGlobalLock,
-                    mData,
-                    mUid,
-                    touchedWin.getOwningPackage(),
-                    mFlags & DRAG_FLAGS_URI_PERMISSIONS,
-                    mSourceUserId,
-                    targetUserId);
-        } else {
-            dragAndDropPermissions = null;
-        }
-        if (mSourceUserId != targetUserId) {
-            if (mData != null) {
-                mData.fixUris(mSourceUserId);
-            }
-        }
         final IBinder clientToken = touchedWin.mClient.asBinder();
-        final DragEvent event = obtainDragEvent(DragEvent.ACTION_DROP, x, y,
-                mData, targetInterceptsGlobalDrag(touchedWin),
-                dragAndDropPermissions);
+        final DragEvent event = createDropEvent(x, y, touchedWin, false /* includePrivateInfo */);
         try {
             touchedWin.mClient.dispatchDragEvent(event);
 
@@ -345,7 +376,7 @@
                     DragDropController.DRAG_TIMEOUT_MS);
         } catch (RemoteException e) {
             Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
-            endDragLocked();
+            endDragLocked(false /* consumed */, false /* relinquishDragSurfaceToDropTarget */);
             return false;
         } finally {
             if (MY_PID != touchedWin.mSession.mPid) {
@@ -353,6 +384,7 @@
             }
         }
         mToken = clientToken;
+        mUnhandledDropEvent = unhandledDropEvent;
         return true;
     }
 
@@ -478,6 +510,9 @@
             boolean containsAppExtras) {
         final boolean interceptsGlobalDrag = targetInterceptsGlobalDrag(newWin);
         if (mDragInProgress && isValidDropTarget(newWin, containsAppExtras, interceptsGlobalDrag)) {
+            if (DEBUG_DRAG) {
+                Slog.d(TAG_WM, "Sending DRAG_STARTED to new window " + newWin);
+            }
             // Only allow the extras to be dispatched to a global-intercepting drag target
             ClipData data = interceptsGlobalDrag ? mData.copyForTransferWithActivityInfo() : null;
             DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED,
@@ -523,14 +558,25 @@
             return false;
         }
         if (!targetWin.isPotentialDragTarget(interceptsGlobalDrag)) {
+            // Window should not be a target
             return false;
         }
-        if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0 || !targetWindowSupportsGlobalDrag(targetWin)) {
+        final boolean isGlobalSameAppDrag = (mFlags & View.DRAG_FLAG_GLOBAL_SAME_APPLICATION) != 0;
+        final boolean isGlobalDrag = (mFlags & View.DRAG_FLAG_GLOBAL) != 0;
+        final boolean isAnyGlobalDrag = isGlobalDrag || isGlobalSameAppDrag;
+        if (!isAnyGlobalDrag || !targetWindowSupportsGlobalDrag(targetWin)) {
             // Drag is limited to the current window.
             if (!isLocalWindow) {
                 return false;
             }
         }
+        if (isGlobalSameAppDrag) {
+            // Drag is limited to app windows from the same uid or windows that can intercept global
+            // drag
+            if (!interceptsGlobalDrag && mUid != targetWin.getUid()) {
+                return false;
+            }
+        }
 
         return interceptsGlobalDrag
                 || mCrossProfileCopyAllowed
@@ -547,7 +593,10 @@
     /**
      * @return whether the given window {@param targetWin} can intercept global drags.
      */
-    public boolean targetInterceptsGlobalDrag(WindowState targetWin) {
+    public boolean targetInterceptsGlobalDrag(@Nullable WindowState targetWin) {
+        if (targetWin == null) {
+            return false;
+        }
         return (targetWin.mAttrs.privateFlags & PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP) != 0;
     }
 
@@ -561,9 +610,6 @@
             if (isWindowNotified(newWin)) {
                 return;
             }
-            if (DEBUG_DRAG) {
-                Slog.d(TAG_WM, "need to send DRAG_STARTED to new window " + newWin);
-            }
             sendDragStartedLocked(newWin, mCurrentX, mCurrentY,
                     containsApplicationExtras(mDataDescription));
         }
@@ -578,7 +624,13 @@
         return false;
     }
 
-    void endDragLocked() {
+    /**
+     * Ends the current drag, animating the drag surface back to the source if the drop was not
+     * consumed by the receiving window.
+     */
+    void endDragLocked(boolean dropConsumed, boolean relinquishDragSurfaceToDropTarget) {
+        mDragResult = dropConsumed;
+        mRelinquishDragSurfaceToDropTarget = relinquishDragSurfaceToDropTarget;
         if (mAnimator != null) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 80894b2..61fde5e 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -465,7 +465,9 @@
             }
         }
         final InsetsSource source = new InsetsSource(id, provider.getType());
-        source.setFrame(provider.getArbitraryRectangle()).updateSideHint(getBounds());
+        source.setFrame(provider.getArbitraryRectangle())
+                .updateSideHint(getBounds())
+                .setBoundingRects(provider.getBoundingRects());
         mLocalInsetsSources.put(id, source);
         mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
     }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4ea76e1..de8d9f9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -310,6 +310,7 @@
 import android.window.ISurfaceSyncGroupCompletedListener;
 import android.window.ITaskFpsCallback;
 import android.window.ITrustedPresentationListener;
+import android.window.IUnhandledDragListener;
 import android.window.InputTransferToken;
 import android.window.ScreenCapture;
 import android.window.SystemPerformanceHinter;
@@ -10026,4 +10027,16 @@
     void onProcessActivityVisibilityChanged(int uid, boolean visible) {
         mScreenRecordingCallbackController.onProcessActivityVisibilityChanged(uid, visible);
     }
+
+    /**
+     * Sets the listener to be called back when a cross-window drag and drop operation is unhandled
+     * (ie. not handled by any window which can handle the drag).
+     */
+    @Override
+    public void setUnhandledDragListener(IUnhandledDragListener listener) throws RemoteException {
+        mAtmService.enforceTaskPermission("setUnhandledDragListener");
+        synchronized (mGlobalLock) {
+            mDragDropController.setUnhandledDragListener(listener);
+        }
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 05d1c49..85eac29 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -98,6 +98,7 @@
 import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS;
 import static android.app.AppOpsManager.OPSTR_SYSTEM_EXEMPT_FROM_SUSPENSION;
 import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
+import static android.app.admin.DeviceAdminInfo.HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
 import static android.app.admin.DeviceAdminInfo.USES_POLICY_FORCE_LOCK;
 import static android.app.admin.DeviceAdminInfo.USES_POLICY_WIPE_DATA;
 import static android.app.admin.DeviceAdminReceiver.ACTION_COMPLIANCE_ACKNOWLEDGEMENT_REQUIRED;
@@ -189,6 +190,7 @@
 import static android.app.admin.DevicePolicyManager.STATUS_MANAGED_USERS_NOT_SUPPORTED;
 import static android.app.admin.DevicePolicyManager.STATUS_NONSYSTEM_USER_EXISTS;
 import static android.app.admin.DevicePolicyManager.STATUS_NOT_SYSTEM_USER;
+import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_ONLY_SYSTEM_USER;
 import static android.app.admin.DevicePolicyManager.STATUS_OK;
 import static android.app.admin.DevicePolicyManager.STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
 import static android.app.admin.DevicePolicyManager.STATUS_SYSTEM_USER;
@@ -225,6 +227,7 @@
 import static android.app.admin.ProvisioningException.ERROR_STARTING_PROFILE_FAILED;
 import static android.app.admin.flags.Flags.backupServiceSecurityLogEventEnabled;
 import static android.app.admin.flags.Flags.dumpsysPolicyEngineMigrationEnabled;
+import static android.app.admin.flags.Flags.headlessDeviceOwnerSingleUserEnabled;
 import static android.app.admin.flags.Flags.policyEngineMigrationV2Enabled;
 import static android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE;
 import static android.content.Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE;
@@ -9460,7 +9463,8 @@
         }
 
         if (setProfileOwnerOnCurrentUserIfNecessary
-                && mInjector.userManagerIsHeadlessSystemUserMode()) {
+                && mInjector.userManagerIsHeadlessSystemUserMode()
+                && getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED) {
             int currentForegroundUser;
             synchronized (getLockObject()) {
                 currentForegroundUser = getCurrentForegroundUserId();
@@ -9476,6 +9480,12 @@
         return true;
     }
 
+    private int getHeadlessDeviceOwnerMode() {
+        synchronized (getLockObject()) {
+            return getDeviceOwnerAdminLocked().info.getHeadlessDeviceOwnerMode();
+        }
+    }
+
     /**
      * This API is cached: invalidate with invalidateBinderCaches().
      */
@@ -12226,6 +12236,13 @@
                     + admin + " are not in the same package");
         }
         final CallerIdentity caller = getCallerIdentity(admin);
+
+        if (headlessDeviceOwnerSingleUserEnabled()) {
+            // Block this method if the device is in headless main user mode
+            Preconditions.checkCallAuthorization(
+                    getHeadlessDeviceOwnerMode() != HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER,
+                    "createAndManageUser was called while in headless single user mode");
+        }
         // Only allow the system user to use this method
         Preconditions.checkCallAuthorization(caller.getUserHandle().isSystem(),
                 "createAndManageUser was called from non-system user");
@@ -16636,29 +16653,51 @@
             return STATUS_USER_NOT_RUNNING;
         }
 
+        DeviceAdminInfo adminInfo = null;
+
+        boolean isHeadlessModeAffiliated = false;
+
+        boolean isHeadlessModeSingleUser = false;
+
         boolean isHeadlessSystemUserMode = mInjector.userManagerIsHeadlessSystemUserMode();
 
+        int ensureSetUpUser = UserHandle.USER_SYSTEM;
         if (isHeadlessSystemUserMode) {
-            if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
+            if (owner != null) {
+                adminInfo = findAdmin(owner,
+                        deviceOwnerUserId, /* throwForMissingPermission= */ false);
+
+                isHeadlessModeAffiliated =
+                        adminInfo.getHeadlessDeviceOwnerMode()
+                                == HEADLESS_DEVICE_OWNER_MODE_AFFILIATED;
+
+                isHeadlessModeSingleUser =
+                        adminInfo.getHeadlessDeviceOwnerMode()
+                                == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER;
+
+                if (!isHeadlessModeAffiliated && !isHeadlessModeSingleUser) {
+                    return STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED;
+                }
+
+                if (headlessDeviceOwnerSingleUserEnabled() && isHeadlessModeSingleUser) {
+                    ensureSetUpUser = mUserManagerInternal.getMainUserId();
+                    if (ensureSetUpUser == UserHandle.USER_NULL) {
+                        return STATUS_HEADLESS_ONLY_SYSTEM_USER;
+                    }
+                }
+            }
+
+            if (isHeadlessModeAffiliated && deviceOwnerUserId != UserHandle.USER_SYSTEM) {
                 Slogf.e(LOG_TAG, "In headless system user mode, "
                         + "device owner can only be set on headless system user.");
                 return STATUS_NOT_SYSTEM_USER;
             }
 
-            if (owner != null) {
-                DeviceAdminInfo adminInfo = findAdmin(
-                        owner, deviceOwnerUserId, /* throwForMissingPermission= */ false);
-
-                if (adminInfo.getHeadlessDeviceOwnerMode()
-                        != HEADLESS_DEVICE_OWNER_MODE_AFFILIATED) {
-                    return STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED;
-                }
-            }
         }
 
         if (isAdb) {
             // If shell command runs after user setup completed check device status. Otherwise, OK.
-            if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
+            if (hasUserSetupCompleted(ensureSetUpUser)) {
                 // DO can be setup only if there are no users which are neither created by default
                 // nor marked as FOR_TESTING
 
@@ -16681,11 +16720,12 @@
             return STATUS_OK;
         } else {
             // DO has to be user 0
-            if (deviceOwnerUserId != UserHandle.USER_SYSTEM) {
+            if ((!isHeadlessSystemUserMode || isHeadlessModeAffiliated)
+                    && deviceOwnerUserId != UserHandle.USER_SYSTEM) {
                 return STATUS_NOT_SYSTEM_USER;
             }
             // Only provision DO before setup wizard completes
-            if (hasUserSetupCompleted(UserHandle.USER_SYSTEM)) {
+            if (hasUserSetupCompleted(ensureSetUpUser)) {
                 return STATUS_USER_SETUP_COMPLETED;
             }
             return STATUS_OK;
@@ -21260,7 +21300,11 @@
             setTimeAndTimezone(provisioningParams.getTimeZone(), provisioningParams.getLocalTime());
             setLocale(provisioningParams.getLocale());
 
-            int deviceOwnerUserId = UserHandle.USER_SYSTEM;
+            int deviceOwnerUserId = headlessDeviceOwnerSingleUserEnabled()
+                    && getHeadlessDeviceOwnerMode() == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER
+                    ? mUserManagerInternal.getMainUserId()
+                    : UserHandle.USER_SYSTEM;
+
             if (!removeNonRequiredAppsForManagedDevice(
                     deviceOwnerUserId,
                     provisioningParams.isLeaveAllSystemAppsEnabled(),
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
index 8b22718..bc264a4 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
@@ -16,11 +16,12 @@
 
 package com.android.server.policy;
 
-import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
-import static com.android.server.devicestate.DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
-import static com.android.server.devicestate.DeviceState.FLAG_EMULATED_ONLY;
-import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
-import static com.android.server.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
+import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_OVERRIDE_REQUESTS;
+import static android.hardware.devicestate.DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
+import static android.hardware.devicestate.DeviceState.FLAG_EMULATED_ONLY;
+import static android.hardware.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_POWER_SAVE_MODE;
+import static android.hardware.devicestate.DeviceState.FLAG_UNSUPPORTED_WHEN_THERMAL_STATUS_CRITICAL;
+
 import static com.android.server.policy.BookStyleStateTransitions.DEFAULT_STATE_TRANSITIONS;
 import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createConfig;
 import static com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration.createTentModeClosedState;
@@ -36,7 +37,6 @@
 import com.android.server.devicestate.DeviceStateProvider;
 import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration;
 import com.android.server.policy.feature.flags.FeatureFlags;
-import com.android.server.policy.feature.flags.FeatureFlagsImpl;
 
 import java.io.PrintWriter;
 import java.util.function.Predicate;
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
index 021a667..bf2619b 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
@@ -34,6 +34,7 @@
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.display.DisplayManager;
 import android.os.Handler;
 import android.os.Looper;
@@ -48,7 +49,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
-import com.android.server.devicestate.DeviceState;
 import com.android.server.devicestate.DeviceStateProvider;
 import com.android.server.policy.feature.flags.FeatureFlags;
 import com.android.server.policy.feature.flags.FeatureFlagsImpl;
diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
index 04cebab..930f4a6 100644
--- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
+++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/FoldableDeviceStateProviderTest.java
@@ -51,6 +51,7 @@
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.display.DisplayManager;
 import android.hardware.input.InputSensorInfo;
 import android.os.Handler;
@@ -58,7 +59,6 @@
 import android.testing.AndroidTestingRunner;
 import android.view.Display;
 
-import com.android.server.devicestate.DeviceState;
 import com.android.server.devicestate.DeviceStateProvider.Listener;
 import com.android.server.policy.FoldableDeviceStateProvider.DeviceStateConfiguration;
 import com.android.server.policy.feature.flags.FakeFeatureFlagsImpl;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c55d709..dbbfb03 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -48,7 +48,6 @@
 import android.content.res.Configuration;
 import android.content.res.Resources.Theme;
 import android.credentials.CredentialManager;
-import android.credentials.flags.Flags;
 import android.database.sqlite.SQLiteCompatibilityWalFlags;
 import android.database.sqlite.SQLiteGlobal;
 import android.graphics.GraphicsStatsService;
@@ -464,6 +463,11 @@
     private static final String DEVICE_LOCK_APEX_PATH =
             "/apex/com.android.devicelock/javalib/service-devicelock.jar";
 
+    private static final String PROFILING_SERVICE_LIFECYCLE_CLASS =
+            "android.os.profiling.ProfilingService$Lifecycle";
+    private static final String PROFILING_SERVICE_JAR_PATH =
+            "/apex/com.android.profiling/javalib/service-profiling.jar";
+
     private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
 
     private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
@@ -2774,6 +2778,14 @@
         mSystemServiceManager.startService(ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS);
         t.traceEnd();
 
+        // Profiling
+        if (android.server.Flags.telemetryApisService()) {
+            t.traceBegin("StartProfilingCompanion");
+            mSystemServiceManager.startServiceFromJar(PROFILING_SERVICE_LIFECYCLE_CLASS,
+                    PROFILING_SERVICE_JAR_PATH);
+            t.traceEnd();
+        }
+
         if (safeMode) {
             mActivityManagerService.enterSafeMode();
         }
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
index 885ed35..b9f00d7 100644
--- a/services/people/java/com/android/server/people/PeopleService.java
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -38,6 +38,7 @@
 import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -474,6 +475,10 @@
             getDataManager().restore(userId, payload);
         }
 
+        @Override
+        public void requestServiceFeatures(AppPredictionSessionId sessionId,
+                IRemoteCallback callback) {}
+
         @VisibleForTesting
         SessionInfo getSessionInfo(AppPredictionSessionId sessionId) {
             return mSessions.get(sessionId);
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 2f5c109..67f66de 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -2958,7 +2958,7 @@
         /** These permissions are supported for virtual devices. */
         // TODO: b/298661870 - Use new API to get the list of device aware permissions.
         val DEVICE_AWARE_PERMISSIONS =
-            if (Flags.deviceAwarePermissionApisEnabled()) {
+            if (Flags.deviceAwarePermissionsEnabled()) {
                 setOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
             } else {
                 emptySet<String>()
diff --git a/services/tests/mockingservicestests/src/com/android/server/OWNERS b/services/tests/mockingservicestests/src/com/android/server/OWNERS
index c0f0ce0..b363f54 100644
--- a/services/tests/mockingservicestests/src/com/android/server/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/OWNERS
@@ -1,3 +1,4 @@
 per-file *Alarm* = file:/apex/jobscheduler/OWNERS
 per-file *AppStateTracker* = file:/apex/jobscheduler/OWNERS
 per-file *DeviceIdleController* = file:/apex/jobscheduler/OWNERS
+per-file SensitiveContentProtectionManagerServiceTest.java = file:/core/java/android/permission/OWNERS
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
index 395b3aa..d36b553 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
@@ -95,24 +95,24 @@
         assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
-                null, 0, 3_600_000, 90, 1_000_000);
+                null, 0, -1, 3_600_000, 90, 1_000_000);
 
         assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE, BatteryStats.HistoryItem.EVENT_NONE,
-                null, 0, 2_400_000, 80, 2_000_000);
+                null, 0, 3700, 2_400_000, 80, 2_000_000);
 
         assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE,
                 BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_START,
-                "foo", APP_UID, 2_400_000, 80, 3_000_000);
+                "foo", APP_UID, 3700, 2_400_000, 80, 3_000_000);
 
         assertThat(item = iterator.next()).isNotNull();
         assertHistoryItem(item,
                 BatteryStats.HistoryItem.CMD_UPDATE,
                 BatteryStats.HistoryItem.EVENT_ALARM | BatteryStats.HistoryItem.EVENT_FLAG_FINISH,
-                "foo", APP_UID, 2_400_000, 80, 3_001_000);
+                "foo", APP_UID, 3700, 2_400_000, 80, 3_001_000);
 
         assertThat(iterator.hasNext()).isFalse();
         assertThat(iterator.next()).isNull();
@@ -140,7 +140,7 @@
         mMockClock.currentTime = 3000;
 
         mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
-                100, /* plugType */ 0, 90, 72, 3700, 3_600_000, 4_000_000, 0, 1_000_000,
+                100, /* plugType */ 0, 90, 72, -1, 3_600_000, 4_000_000, 0, 1_000_000,
                 1_000_000, 1_000_000);
         mBatteryStats.setBatteryStateLocked(BatteryManager.BATTERY_STATUS_DISCHARGING,
                 100, /* plugType */ 0, 80, 72, 3700, 2_400_000, 4_000_000, 0, 2_000_000,
@@ -303,7 +303,7 @@
     }
 
     private void assertHistoryItem(BatteryStats.HistoryItem item, int command, int eventCode,
-            String tag, int uid, int batteryChargeUah, int batteryLevel,
+            String tag, int uid, int voltageMv, int batteryChargeUah, int batteryLevel,
             long elapsedTimeMs) {
         assertThat(item.cmd).isEqualTo(command);
         assertThat(item.eventCode).isEqualTo(eventCode);
@@ -313,6 +313,7 @@
             assertThat(item.eventTag.string).isEqualTo(tag);
             assertThat(item.eventTag.uid).isEqualTo(uid);
         }
+        assertThat((int) item.batteryVoltage).isEqualTo(voltageMv);
         assertThat(item.batteryChargeUah).isEqualTo(batteryChargeUah);
         assertThat(item.batteryLevel).isEqualTo(batteryLevel);
 
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 26934d8..4307ec5 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -682,19 +682,19 @@
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
 
         setUpAndStartUserInBackground(TEST_USER_ID);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback= */ null, /* expectLocking= */ true);
 
         setUpAndStartUserInBackground(TEST_USER_ID1);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
 
         setUpAndStartUserInBackground(TEST_USER_ID2);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ false,
                 /* keyEvictedCallback= */ null, /* expectLocking= */ true);
 
         setUpAndStartUserInBackground(TEST_USER_ID3);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* allowDelayedLocking= */ false,
                 /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
     }
 
@@ -739,21 +739,21 @@
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
 
-        // delayedLocking set and no KeyEvictedCallback, so it should not lock.
+        // allowDelayedLocking set and no KeyEvictedCallback, so it should not lock.
         setUpAndStartUserInBackground(TEST_USER_ID);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback= */ null, /* expectLocking= */ false);
 
         setUpAndStartUserInBackground(TEST_USER_ID1);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
 
         setUpAndStartUserInBackground(TEST_USER_ID2);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ false,
                 /* keyEvictedCallback= */ null, /* expectLocking= */ true);
 
         setUpAndStartUserInBackground(TEST_USER_ID3);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* allowDelayedLocking= */ false,
                 /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
     }
 
@@ -843,7 +843,7 @@
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ false);
     }
 
@@ -855,19 +855,20 @@
         mSetFlagsRule.disableFlags(
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ true);
 
         mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
         mSetFlagsRule.enableFlags(
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
         setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ true,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ true);
     }
 
+    /** Delayed-locking users (as opposed to devices) have no limits on how many can be unlocked. */
     @Test
-    public void testStopPrivateProfileWithDelayedLocking_maxRunningUsersBreached()
+    public void testStopPrivateProfileWithDelayedLocking_imperviousToNumberOfRunningUsers()
             throws Exception {
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
                 /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false);
@@ -875,10 +876,14 @@
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
         setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
-                /* keyEvictedCallback */ null, /* expectLocking= */ true);
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
+                /* keyEvictedCallback */ null, /* expectLocking= */ false);
     }
 
+    /**
+        * Tests that when a device/user (managed profile) does not permit delayed locking, then
+        * even if allowDelayedLocking is true, the user will still be locked.
+    */
     @Test
     public void testStopManagedProfileWithDelayedLocking() throws Exception {
         mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
@@ -886,7 +891,7 @@
         mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
                 android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
         setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
-        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+        assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
                 /* keyEvictedCallback */ null, /* expectLocking= */ true);
     }
 
@@ -1087,29 +1092,29 @@
         mUserStates.put(userId, mUserController.getStartedUserState(userId));
     }
 
-    private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean delayedLocking,
+    private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean allowDelayedLocking,
             KeyEvictedCallback keyEvictedCallback, boolean expectLocking) throws Exception {
-        int r = mUserController.stopUser(userId, /* force= */ true, /* delayedLocking= */
-                delayedLocking, null, keyEvictedCallback);
+        int r = mUserController.stopUser(userId, /* force= */ true, /* allowDelayedLocking= */
+                allowDelayedLocking, null, keyEvictedCallback);
         assertThat(r).isEqualTo(ActivityManager.USER_OP_SUCCESS);
-        assertUserLockedOrUnlockedState(userId, delayedLocking, expectLocking);
+        assertUserLockedOrUnlockedState(userId, allowDelayedLocking, expectLocking);
     }
 
     private void assertProfileLockedOrUnlockedAfterStopping(int userId, boolean expectLocking)
             throws Exception {
         boolean profileStopped = mUserController.stopProfile(userId);
         assertThat(profileStopped).isTrue();
-        assertUserLockedOrUnlockedState(userId, /* delayedLocking= */ false, expectLocking);
+        assertUserLockedOrUnlockedState(userId, /* allowDelayedLocking= */ false, expectLocking);
     }
 
-    private void assertUserLockedOrUnlockedState(int userId, boolean delayedLocking,
+    private void assertUserLockedOrUnlockedState(int userId, boolean allowDelayedLocking,
             boolean expectLocking) throws InterruptedException, RemoteException {
         // fake all interim steps
         UserState ussUser = mUserStates.get(userId);
         ussUser.setState(UserState.STATE_SHUTDOWN);
         // Passing delayedLocking invalidates incorrect internal data passing but currently there is
         // no easy way to get that information passed through lambda.
-        mUserController.finishUserStopped(ussUser, delayedLocking);
+        mUserController.finishUserStopped(ussUser, allowDelayedLocking);
         waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
         verify(mInjector.mStorageManagerMock, times(expectLocking ? 1 : 0))
                 .lockCeStorage(userId);
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index fa39364..b705077 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -31,6 +31,7 @@
 import static org.testng.Assert.assertThrows;
 
 import android.annotation.NonNull;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateInfo;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.hardware.devicestate.IDeviceStateManagerCallback;
@@ -72,7 +73,8 @@
             new DeviceState(0, "DEFAULT", 0 /* flags */);
     private static final DeviceState OTHER_DEVICE_STATE =
             new DeviceState(1, "OTHER", 0 /* flags */);
-    private static final DeviceState DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
+    private static final DeviceState
+            DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP =
             new DeviceState(2, "DEVICE_STATE_CANCEL_WHEN_REQUESTER_NOT_ON_TOP",
                     DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP /* flags */);
     // A device state that is not reported as being supported for the default test provider.
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index b3d25f2..cfdb586 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -25,6 +25,7 @@
 import static junit.framework.Assert.assertNull;
 
 import android.annotation.Nullable;
+import android.hardware.devicestate.DeviceState;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
@@ -48,8 +49,10 @@
 @RunWith(AndroidJUnit4.class)
 public final class OverrideRequestControllerTest {
 
-    private static final DeviceState TEST_DEVICE_STATE_ZERO = new DeviceState(0, "TEST_STATE", 0);
-    private static final DeviceState TEST_DEVICE_STATE_ONE = new DeviceState(1, "TEST_STATE", 0);
+    private static final DeviceState
+            TEST_DEVICE_STATE_ZERO = new DeviceState(0, "TEST_STATE", 0);
+    private static final DeviceState
+            TEST_DEVICE_STATE_ONE = new DeviceState(1, "TEST_STATE", 0);
 
     private TestStatusChangeListener mStatusListener;
     private OverrideRequestController mController;
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 7e40f96..16909ab 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -40,12 +40,12 @@
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorManager;
+import android.hardware.devicestate.DeviceState;
 import android.os.PowerManager;
 
 import androidx.annotation.NonNull;
 
 import com.android.server.LocalServices;
-import com.android.server.devicestate.DeviceState;
 import com.android.server.devicestate.DeviceStateProvider;
 import com.android.server.input.InputManagerInternal;
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 344a4b0..4dded1d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -16,6 +16,7 @@
 package com.android.server.notification;
 
 import static android.content.Context.DEVICE_POLICY_SERVICE;
+import static android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR;
 import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
 import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
 import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
@@ -62,6 +63,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -1983,6 +1985,22 @@
                 new ComponentName("pkg1", "cmp1"))).isFalse();
     }
 
+    @Test
+    @EnableFlags(FLAG_LIFETIME_EXTENSION_REFACTOR)
+    public void testManagedServiceInfoIsSystemUi() {
+        ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+                APPROVAL_BY_COMPONENT);
+
+        ManagedServices.ManagedServiceInfo service0 = service.new ManagedServiceInfo(
+                mock(IInterface.class), ComponentName.unflattenFromString("a/a"), 0, false,
+                mock(ServiceConnection.class), 26, 34);
+
+        service0.isSystemUi = true;
+        assertThat(service0.isSystemUi()).isTrue();
+        service0.isSystemUi = false;
+        assertThat(service0.isSystemUi()).isFalse();
+    }
+
     private void mockServiceInfoWithMetaData(List<ComponentName> componentNames,
             ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas)
             throws RemoteException {
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 96ffec1..cb9490e 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -303,7 +303,6 @@
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.ClassRule;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -2545,6 +2544,17 @@
         assertThat(mBinderService.getActiveNotifications(sbn.getPackageName()).length).isEqualTo(1);
         assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
 
+        // Checks that a post update is sent.
+        verify(mWorkerHandler, times(1))
+                .post(any(NotificationManagerService.PostNotificationRunnable.class));
+        ArgumentCaptor<NotificationRecord> captor =
+                ArgumentCaptor.forClass(NotificationRecord.class);
+        verify(mListeners, times(1)).prepareNotifyPostedLocked(captor.capture(), any(),
+                anyBoolean());
+        assertThat(captor.getValue().getNotification().flags
+                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
+                FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
+
         mSetFlagsRule.disableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
         mBinderService.cancelNotificationWithTag(PKG, PKG, sbn.getTag(), sbn.getId(),
                 sbn.getUserId());
@@ -2577,6 +2587,17 @@
         StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
         assertThat(notifs.length).isEqualTo(1);
         assertThat(notifs[0].getId()).isEqualTo(1);
+
+        // Checks that a post update is sent.
+        verify(mWorkerHandler, times(1))
+                .post(any(NotificationManagerService.PostNotificationRunnable.class));
+        ArgumentCaptor<NotificationRecord> captor =
+                ArgumentCaptor.forClass(NotificationRecord.class);
+        verify(mListeners, times(1)).prepareNotifyPostedLocked(captor.capture(), any(),
+                anyBoolean());
+        assertThat(captor.getValue().getNotification().flags
+                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
+                FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
     }
 
     @Test
@@ -2985,18 +3006,29 @@
     public void testCancelNotificationsFromListener_clearAll_NoClearLifetimeExt()
             throws Exception {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_LIFETIME_EXTENSION_REFACTOR);
-
         final NotificationRecord notif = generateNotificationRecord(
                 mTestNotificationChannel, 1, null, false);
-        notif.getNotification().flags = FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
+        notif.getNotification().flags |= FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY;
         mService.addNotification(notif);
-
+        verify(mWorkerHandler, times(0))
+                .post(any(NotificationManagerService.PostNotificationRunnable.class));
         mService.getBinderService().cancelNotificationsFromListener(null, null);
         waitForIdle();
-
+        // Notification not cancelled.
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
         assertThat(notifs.length).isEqualTo(1);
+
+        // Checks that a post update is sent.
+        verify(mWorkerHandler, times(1))
+                .post(any(NotificationManagerService.PostNotificationRunnable.class));
+        ArgumentCaptor<NotificationRecord> captor =
+                ArgumentCaptor.forClass(NotificationRecord.class);
+        verify(mListeners, times(1)).prepareNotifyPostedLocked(captor.capture(), any(),
+                anyBoolean());
+        assertThat(captor.getValue().getNotification().flags
+                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
+                FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
     }
 
     @Test
@@ -3217,6 +3249,17 @@
         StatusBarNotification[] notifs =
                 mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
         assertEquals(1, notifs.length);
+
+        // Checks that a post update is sent.
+        verify(mWorkerHandler, times(1))
+                .post(any(NotificationManagerService.PostNotificationRunnable.class));
+        ArgumentCaptor<NotificationRecord> captor =
+                ArgumentCaptor.forClass(NotificationRecord.class);
+        verify(mListeners, times(1)).prepareNotifyPostedLocked(captor.capture(), any(),
+                anyBoolean());
+        assertThat(captor.getValue().getNotification().flags
+                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
+                FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
     }
 
     @Test
@@ -3959,6 +4002,69 @@
     }
 
     @Test
+    public void testUpdateNotificationChannelFromPrivilegedListener_noSoundUriPermission()
+            throws Exception {
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
+                .thenReturn(singletonList(mock(AssociationInfo.class)));
+        when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
+                eq(mTestNotificationChannel.getId()), anyBoolean()))
+                .thenReturn(mTestNotificationChannel);
+
+        final Uri soundUri = Uri.parse("content://media/test/sound/uri");
+        final NotificationChannel updatedNotificationChannel = new NotificationChannel(
+                TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+        updatedNotificationChannel.setSound(soundUri,
+                updatedNotificationChannel.getAudioAttributes());
+
+        doThrow(new SecurityException("no access")).when(mUgmInternal)
+                .checkGrantUriPermission(eq(Process.myUid()), any(), eq(soundUri),
+                anyInt(), eq(Process.myUserHandle().getIdentifier()));
+
+        assertThrows(SecurityException.class,
+                () -> mBinderService.updateNotificationChannelFromPrivilegedListener(null, PKG,
+                Process.myUserHandle(), updatedNotificationChannel));
+
+        verify(mPreferencesHelper, never()).updateNotificationChannel(
+                anyString(), anyInt(), any(), anyBoolean(),  anyInt(), anyBoolean());
+
+        verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
+                eq(Process.myUserHandle()), eq(mTestNotificationChannel),
+                eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
+    }
+
+    @Test
+    public void testUpdateNotificationChannelFromPrivilegedListener_noSoundUriPermission_sameSound()
+            throws Exception {
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mCompanionMgr.getAssociations(PKG, mUserId))
+                .thenReturn(singletonList(mock(AssociationInfo.class)));
+        when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
+                eq(mTestNotificationChannel.getId()), anyBoolean()))
+                .thenReturn(mTestNotificationChannel);
+
+        final Uri soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
+        final NotificationChannel updatedNotificationChannel = new NotificationChannel(
+                TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+        updatedNotificationChannel.setSound(soundUri,
+                updatedNotificationChannel.getAudioAttributes());
+
+        doThrow(new SecurityException("no access")).when(mUgmInternal)
+                .checkGrantUriPermission(eq(Process.myUid()), any(), eq(soundUri),
+                    anyInt(), eq(Process.myUserHandle().getIdentifier()));
+
+        mBinderService.updateNotificationChannelFromPrivilegedListener(
+                null, PKG, Process.myUserHandle(), updatedNotificationChannel);
+
+        verify(mPreferencesHelper, times(1)).updateNotificationChannel(
+                anyString(), anyInt(), any(), anyBoolean(),  anyInt(), anyBoolean());
+
+        verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
+                eq(Process.myUserHandle()), eq(mTestNotificationChannel),
+                eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED));
+    }
+
+    @Test
     public void testGetNotificationChannelFromPrivilegedListener_cdm_success() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mCompanionMgr.getAssociations(PKG, mUserId))
@@ -5659,6 +5765,17 @@
         StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
         assertThat(notifsAfter.length).isEqualTo(1);
         assertThat(mService.getNotificationRecord(notif.getKey())).isEqualTo(notif);
+
+        // Checks that a post update is sent.
+        verify(mWorkerHandler, times(1))
+                .post(any(NotificationManagerService.PostNotificationRunnable.class));
+        ArgumentCaptor<NotificationRecord> captor =
+                ArgumentCaptor.forClass(NotificationRecord.class);
+        verify(mListeners, times(1)).prepareNotifyPostedLocked(captor.capture(), any(),
+                anyBoolean());
+        assertThat(captor.getValue().getNotification().flags
+                & FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY).isEqualTo(
+                FLAG_LIFETIME_EXTENDED_BY_DIRECT_REPLY);
     }
 
     @Test
@@ -14313,7 +14430,6 @@
     }
 
     @Test
-    @Ignore("b/324348078")
     public void cancelNotificationsFromListener_rapidClear_old_cancelOne() throws RemoteException {
         mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
                 .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
@@ -14403,7 +14519,6 @@
     }
 
     @Test
-    @Ignore("b/324348078")
     public void cancelNotificationsFromListener_rapidClear_old_cancelAll() throws RemoteException {
         mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
                 .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 1fb7cd8..9e2b1ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -32,14 +32,17 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.wm.DragDropController.MSG_UNHANDLED_DROP_LISTENER_TIMEOUT;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.app.PendingIntent;
@@ -49,9 +52,12 @@
 import android.content.pm.ShortcutServiceInternal;
 import android.graphics.PixelFormat;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Message;
 import android.os.Parcelable;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.view.DragEvent;
@@ -61,6 +67,7 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
+import android.window.IUnhandledDragListener;
 
 import androidx.test.filters.SmallTest;
 
@@ -533,14 +540,98 @@
                 });
     }
 
+    @Test
+    public void testUnhandledDragListenerNotCalledForNormalDrags() throws RemoteException {
+        assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
+
+        final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+        doReturn(mock(Binder.class)).when(listener).asBinder();
+        mTarget.setUnhandledDragListener(listener);
+        doDragAndDrop(0, ClipData.newPlainText("label", "Test"), 0, 0);
+        verify(listener, times(0)).onUnhandledDrop(any(), any());
+    }
+
+    @Test
+    public void testUnhandledDragListenerReceivesUnhandledDropOverWindow() {
+        assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
+
+        final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+        doReturn(mock(Binder.class)).when(listener).asBinder();
+        mTarget.setUnhandledDragListener(listener);
+        final int invalidXY = 100_000;
+        startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
+            // Notify the unhandled drag listener
+            mTarget.reportDropWindow(mWindow.mInputChannelToken, invalidXY, invalidXY);
+            mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+            mTarget.reportDropResult(mWindow.mClient, false);
+            mTarget.onUnhandledDropCallback(true);
+            mToken = null;
+            try {
+                verify(listener, times(1)).onUnhandledDrop(any(), any());
+            } catch (RemoteException e) {
+                fail("Failed to verify unhandled drop: " + e);
+            }
+        });
+    }
+
+    @Test
+    public void testUnhandledDragListenerReceivesUnhandledDropOverNoValidWindow() {
+        assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
+
+        final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+        doReturn(mock(Binder.class)).when(listener).asBinder();
+        mTarget.setUnhandledDragListener(listener);
+        final int invalidXY = 100_000;
+        startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
+            // Notify the unhandled drag listener
+            mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
+            mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+            mTarget.onUnhandledDropCallback(true);
+            mToken = null;
+            try {
+                verify(listener, times(1)).onUnhandledDrop(any(), any());
+            } catch (RemoteException e) {
+                fail("Failed to verify unhandled drop: " + e);
+            }
+        });
+    }
+
+    @Test
+    public void testUnhandledDragListenerCallbackTimeout() {
+        assumeTrue(com.android.window.flags.Flags.delegateUnhandledDrags());
+
+        final IUnhandledDragListener listener = mock(IUnhandledDragListener.class);
+        doReturn(mock(Binder.class)).when(listener).asBinder();
+        mTarget.setUnhandledDragListener(listener);
+        final int invalidXY = 100_000;
+        startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> {
+            // Notify the unhandled drag listener
+            mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY);
+            mTarget.handleMotionEvent(false /* keepHandling */, invalidXY, invalidXY);
+
+            // Verify that the unhandled drop listener callback timeout has been scheduled
+            final Handler handler = mTarget.getHandler();
+            assertTrue(handler.hasMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT));
+
+            // Force trigger the timeout and verify that it actually cleans up the drag & timeout
+            handler.handleMessage(Message.obtain(handler, MSG_UNHANDLED_DROP_LISTENER_TIMEOUT));
+            assertFalse(handler.hasMessages(MSG_UNHANDLED_DROP_LISTENER_TIMEOUT));
+            assertFalse(mTarget.dragDropActiveLocked());
+            mToken = null;
+        });
+    }
+
     private void doDragAndDrop(int flags, ClipData data, float dropX, float dropY) {
         startDrag(flags, data, () -> {
             mTarget.reportDropWindow(mWindow.mInputChannelToken, dropX, dropY);
-            mTarget.handleMotionEvent(false, dropX, dropY);
+            mTarget.handleMotionEvent(false /* keepHandling */, dropX, dropY);
             mToken = mWindow.mClient.asBinder();
         });
     }
 
+    /**
+     * Starts a drag with the given parameters, calls Runnable `r` after drag is started.
+     */
     private void startDrag(int flag, ClipData data, Runnable r) {
         final SurfaceSession appSession = new SurfaceSession();
         try {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index aa9c0c8..03b695d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -48,6 +48,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -901,7 +902,8 @@
                 new Binder(),
                 0 /* index */,
                 WindowInsets.Type.systemOverlays(),
-                new Rect(0, 0, 1080, 200));
+                new Rect(0, 0, 1080, 200),
+                null /* boundingRects */);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
 
         assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSources
@@ -910,6 +912,31 @@
     }
 
     @Test
+    public void testAddInsetsSource_withBoundingRects() {
+        final Task rootTask = createTask(mDisplayContent);
+
+        final Task navigationBarInsetsReceiverTask = createTaskInRootTask(rootTask, 0);
+        navigationBarInsetsReceiverTask.getConfiguration().windowConfiguration.setBounds(new Rect(
+                0, 200, 1080, 700));
+
+        final Rect[] boundingRects = new Rect[]{
+                new Rect(0, 0, 10, 10), new Rect(100, 100, 200, 100)
+        };
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.addInsetsSource(
+                navigationBarInsetsReceiverTask.mRemoteToken.toWindowContainerToken(),
+                new Binder(),
+                0 /* index */,
+                WindowInsets.Type.systemOverlays(),
+                new Rect(0, 0, 1080, 200),
+                boundingRects);
+        mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
+
+        assertArrayEquals(boundingRects, navigationBarInsetsReceiverTask.mLocalInsetsSources
+                .valueAt(0).getBoundingRects());
+    }
+
+    @Test
     public void testRemoveInsetsSource() {
         final Task rootTask = createTask(mDisplayContent);
 
@@ -923,7 +950,8 @@
                 owner,
                 0 /* index */,
                 WindowInsets.Type.systemOverlays(),
-                new Rect(0, 0, 1080, 200));
+                new Rect(0, 0, 1080, 200),
+                null /* boundingRects */);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
 
         final WindowContainerTransaction wct2 = new WindowContainerTransaction();
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 874c10c..a52614d 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -269,6 +269,27 @@
             "android.telecom.extra.DIAGNOSTIC_MESSAGE";
 
     /**
+     * Boolean indicating that the call is a verified business call.
+     *
+     * {@link Connection#setExtras(Bundle)} or {@link Connection#putExtras(Bundle)}
+     * should be used to notify Telecom this extra has been set.
+     */
+    @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
+    public static final String EXTRA_IS_BUSINESS_CALL =
+            "android.telecom.extra.IS_BUSINESS_CALL";
+
+    /**
+     * String value indicating the asserted display name reported via
+     * ImsCallProfile#EXTRA_ASSERTED_DISPLAY_NAME.
+     *
+     * {@link Connection#setExtras(Bundle)} or {@link Connection#putExtras(Bundle)}
+     * should be used to notify Telecom this extra has been set.
+     */
+    @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
+    public static final String EXTRA_ASSERTED_DISPLAY_NAME =
+            "android.telecom.extra.ASSERTED_DISPLAY_NAME";
+
+    /**
      * Reject reason used with {@link #reject(int)} to indicate that the user is rejecting this
      * call because they have declined to answer it.  This typically means that they are unable
      * to answer the call at this time and would prefer it be sent to voicemail.
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index f7793f3..697c8ec 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9778,6 +9778,13 @@
     public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool";
 
     /**
+     * Indicates if the carrier supports a business call composer.
+     */
+    @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_BUSINESS_CALL_COMPOSER)
+    public static final String KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL =
+            "supports_business_call_composer_bool";
+
+    /**
      * Indicates the carrier server url that serves the call composer picture.
      */
     public static final String KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING =
@@ -10861,6 +10868,7 @@
         sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
         sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
         sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
+        sDefaults.putBoolean(KEY_SUPPORTS_BUSINESS_CALL_COMPOSER_BOOL, false);
         sDefaults.putString(KEY_CALL_COMPOSER_PICTURE_SERVER_URL_STRING, "");
         sDefaults.putBoolean(KEY_USE_ACS_FOR_RCS_BOOL, false);
         sDefaults.putBoolean(KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL, true);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 7118207..cd641b8 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1156,6 +1156,28 @@
      */
     public static final String TRANSFER_STATUS = SimInfo.COLUMN_TRANSFER_STATUS;
 
+    /**
+     * TelephonyProvider column name for satellite entitlement status. The value of this column is
+     * set based on entitlement query result for satellite configuration.
+     * By default, it's disabled.
+     * <P>Type: INTEGER (int)</P>
+     *
+     * @hide
+     */
+    public static final String SATELLITE_ENTITLEMENT_STATUS =
+            SimInfo.COLUMN_SATELLITE_ENTITLEMENT_STATUS;
+
+    /**
+     * TelephonyProvider column name for satellite entitlement plmns. The value of this column is
+     * set based on entitlement query result for satellite configuration.
+     * By default, it's empty.
+     * <P>Type: TEXT </P>
+     *
+     * @hide
+     */
+    public static final String SATELLITE_ENTITLEMENT_PLMNS =
+            SimInfo.COLUMN_SATELLITE_ENTITLEMENT_PLMNS;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"USAGE_SETTING_"},
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 61c7a42..041822b 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -10629,20 +10629,27 @@
     }
 
     /**
-     * Call composer status OFF from user setting.
+     * Call composer status <b>OFF</b> from user setting.
      */
     public static final int CALL_COMPOSER_STATUS_OFF = 0;
 
     /**
-     * Call composer status ON from user setting.
+     * Call composer status <b>ON</b> from user setting.
      */
     public static final int CALL_COMPOSER_STATUS_ON = 1;
 
+    /**
+     * Call composer status <b>Business Only</b> from user setting.
+     */
+    @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_BUSINESS_CALL_COMPOSER)
+    public static final int CALL_COMPOSER_STATUS_BUSINESS_ONLY = 2;
+
     /** @hide */
     @IntDef(prefix = {"CALL_COMPOSER_STATUS_"},
             value = {
                 CALL_COMPOSER_STATUS_ON,
                 CALL_COMPOSER_STATUS_OFF,
+                CALL_COMPOSER_STATUS_BUSINESS_ONLY
             })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CallComposerStatus {}
@@ -10663,9 +10670,16 @@
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     public void setCallComposerStatus(@CallComposerStatus int status) {
-        if (status > CALL_COMPOSER_STATUS_ON
-                || status < CALL_COMPOSER_STATUS_OFF) {
-            throw new IllegalArgumentException("requested status is invalid");
+        if (com.android.server.telecom.flags.Flags.businessCallComposer()) {
+            if (status > CALL_COMPOSER_STATUS_BUSINESS_ONLY
+                    || status < CALL_COMPOSER_STATUS_OFF) {
+                throw new IllegalArgumentException("requested status is invalid");
+            }
+        } else {
+            if (status > CALL_COMPOSER_STATUS_ON
+                    || status < CALL_COMPOSER_STATUS_OFF) {
+                throw new IllegalArgumentException("requested status is invalid");
+            }
         }
         try {
             ITelephony telephony = getITelephony();
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index d07edeb..cebfe01 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -16,6 +16,7 @@
 
 package android.telephony.ims;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -299,6 +300,16 @@
             "android.telephony.ims.extra.IS_BUSINESS_CALL";
 
     /**
+     * The vendor IMS stack populates this {@code string} extra; it is used to hold the display name
+     * passed via the P-Asserted-Identity SIP header’s display-name field
+     *
+     * Reference: RFC3325
+     */
+    @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_BUSINESS_CALL_COMPOSER)
+    public static final String EXTRA_ASSERTED_DISPLAY_NAME =
+            "android.telephony.ims.extra.ASSERTED_DISPLAY_NAME";
+
+    /**
      * Values for EXTRA_OIR / EXTRA_CNAP
      */
     /**
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index 746246c..9789082 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -17,6 +17,7 @@
 package android.telephony.ims.feature;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -59,6 +60,7 @@
 import com.android.ims.internal.IImsMultiEndpoint;
 import com.android.ims.internal.IImsUt;
 import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.server.telecom.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -513,7 +515,8 @@
                         CAPABILITY_TYPE_VIDEO,
                         CAPABILITY_TYPE_UT,
                         CAPABILITY_TYPE_SMS,
-                        CAPABILITY_TYPE_CALL_COMPOSER
+                        CAPABILITY_TYPE_CALL_COMPOSER,
+                        CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY
                 })
         @Retention(RetentionPolicy.SOURCE)
         public @interface MmTelCapability {}
@@ -550,11 +553,19 @@
          */
         public static final int CAPABILITY_TYPE_CALL_COMPOSER = 1 << 4;
 
+
+        /**
+         * This MmTelFeature supports Business-only Call Composer
+         */
+        @FlaggedApi(Flags.FLAG_BUSINESS_CALL_COMPOSER)
+        public static final int CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY = 1 << 5;
+
         /**
          * This is used to check the upper range of MmTel capability
          * @hide
          */
-        public static final int CAPABILITY_TYPE_MAX = CAPABILITY_TYPE_CALL_COMPOSER + 1;
+        public static final int CAPABILITY_TYPE_MAX =
+                CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY + 1;
 
         /**
          * @hide
@@ -601,6 +612,8 @@
             builder.append(isCapable(CAPABILITY_TYPE_SMS));
             builder.append(" CALL_COMPOSER: ");
             builder.append(isCapable(CAPABILITY_TYPE_CALL_COMPOSER));
+            builder.append(" BUSINESS_COMPOSER_ONLY: ");
+            builder.append(isCapable(CAPABILITY_TYPE_CALL_COMPOSER_BUSINESS_ONLY));
             builder.append("]");
             return builder.toString();
         }
diff --git a/tests/UsbManagerTests/Android.bp b/tests/UsbManagerTests/Android.bp
index 70c7dad..a16a7ea 100644
--- a/tests/UsbManagerTests/Android.bp
+++ b/tests/UsbManagerTests/Android.bp
@@ -33,6 +33,8 @@
         "platform-test-annotations",
         "truth",
         "UsbManagerTestLib",
+        "flag-junit",
+        "TestParameterInjector",
     ],
     jni_libs: [
         // Required for ExtendedMockito
diff --git a/tests/UsbManagerTests/src/android/hardware/usb/UsbPortStatusTest.java b/tests/UsbManagerTests/src/android/hardware/usb/UsbPortStatusTest.java
new file mode 100644
index 0000000..dabfcae
--- /dev/null
+++ b/tests/UsbManagerTests/src/android/hardware/usb/UsbPortStatusTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.usb;
+
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_DETECTION_NOT_SUPPORTED;
+import static android.hardware.usb.UsbPortStatus.CONTAMINANT_PROTECTION_NONE;
+import static android.hardware.usb.UsbPortStatus.DATA_ROLE_DEVICE;
+import static android.hardware.usb.UsbPortStatus.DATA_ROLE_HOST;
+import static android.hardware.usb.UsbPortStatus.DATA_ROLE_NONE;
+import static android.hardware.usb.UsbPortStatus.MODE_NONE;
+import static android.hardware.usb.UsbPortStatus.POWER_ROLE_NONE;
+import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SINK;
+import static android.hardware.usb.UsbPortStatus.POWER_ROLE_SOURCE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hardware.usb.flags.Flags;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+
+import com.google.testing.junit.testparameterinjector.TestParameter;
+import com.google.testing.junit.testparameterinjector.TestParameterInjector;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link android.hardware.usb.UsbPortStatus} */
+@RunWith(TestParameterInjector.class)
+public class UsbPortStatusTest {
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_IS_PD_COMPLIANT_API)
+    public void testIsPdCompliant(
+            @TestParameter boolean isSinkDeviceRoleSupported,
+            @TestParameter boolean isSinkHostRoleSupported,
+            @TestParameter boolean isSourceDeviceRoleSupported,
+            @TestParameter boolean isSourceHostRoleSupported) {
+        int supportedRoleCombinations = getSupportedRoleCombinations(
+                isSinkDeviceRoleSupported,
+                isSinkHostRoleSupported,
+                isSourceDeviceRoleSupported,
+                isSinkHostRoleSupported);
+        UsbPortStatus usbPortStatus = new UsbPortStatus(
+                MODE_NONE,
+                POWER_ROLE_NONE,
+                DATA_ROLE_NONE,
+                supportedRoleCombinations,
+                CONTAMINANT_PROTECTION_NONE,
+                CONTAMINANT_DETECTION_NOT_SUPPORTED);
+        boolean expectedResult = isSinkDeviceRoleSupported
+                && isSinkHostRoleSupported
+                && isSourceDeviceRoleSupported
+                && isSourceHostRoleSupported;
+
+        assertThat(usbPortStatus.isPdCompliant()).isEqualTo(expectedResult);
+    }
+
+    private int getSupportedRoleCombinations(
+            boolean isSinkDeviceRoleSupported,
+            boolean isSinkHostRoleSupported,
+            boolean isSourceDeviceRoleSupported,
+            boolean isSourceHostRoleSupported) {
+        int result = UsbPort.combineRolesAsBit(POWER_ROLE_NONE, DATA_ROLE_NONE);
+
+        if (isSinkDeviceRoleSupported) {
+            result |= UsbPort.combineRolesAsBit(POWER_ROLE_SINK, DATA_ROLE_DEVICE);
+        }
+        if (isSinkHostRoleSupported) {
+            result |= UsbPort.combineRolesAsBit(POWER_ROLE_SINK, DATA_ROLE_HOST);
+        }
+        if (isSourceDeviceRoleSupported) {
+            result |= UsbPort.combineRolesAsBit(POWER_ROLE_SOURCE, DATA_ROLE_DEVICE);
+        }
+        if (isSourceHostRoleSupported) {
+            result |= UsbPort.combineRolesAsBit(POWER_ROLE_SOURCE, DATA_ROLE_HOST);
+        }
+
+        return result;
+    }
+}
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 60c25b7..be5c84c 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -14,6 +14,8 @@
 
 package android.testing;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -85,6 +87,57 @@
         setupQueue(looper);
     }
 
+    /**
+     * Wrap the given runnable so that it will run blocking on the Looper that will be set up for
+     * the given test.
+     * <p>
+     * This method is required to support any TestRule which needs to run setup and/or teardown code
+     * on the TestableLooper. Whether using {@link AndroidTestingRunner} or
+     * {@link TestWithLooperRule}, the TestRule's Statement evaluates on the test instrumentation
+     * thread, rather than the TestableLooper thread, so access to the TestableLooper is required.
+     * However, {@link #get(Object)} will return {@code null} both before and after the inner
+     * statement is evaluated:
+     * <ul>
+     * <li>Before the test {@link #get} returns {@code null} because while the TestableLooperHolder
+     * is accessible in sLoopers, it has not been initialized with an actual TestableLooper yet.
+     * This method's use of the internal LooperFrameworkMethod ensures that all setup and teardown
+     * of the TestableLooper happen as it would for all other wrapped code blocks.
+     * <li>After the test {@link #get} can return {@code null} because many tests call
+     * {@link #remove} in the teardown method. The fact that this method returns a runnable allows
+     * it to be called before the test (when the TestableLooperHolder is still in sLoopers), and
+     * then executed as teardown after the test.
+     * </ul>
+     *
+     * @param test     the test instance (just like passed to {@link #get(Object)})
+     * @param runnable the operation that should eventually be run on the TestableLooper
+     * @return a runnable that will block the thread on which it is called until the given runnable
+     *          is finished.  Will be {@code null} if there is no looper for the given test.
+     * @hide
+     */
+    @Nullable
+    public static RunnableWithException wrapWithRunBlocking(
+            Object test, @NonNull RunnableWithException runnable) {
+        TestableLooperHolder looperHolder = sLoopers.get(test);
+        if (looperHolder == null) {
+            return null;
+        }
+        try {
+            FrameworkMethod base = new FrameworkMethod(runnable.getClass().getMethod("run"));
+            LooperFrameworkMethod wrapped = new LooperFrameworkMethod(base, looperHolder);
+            return () -> {
+                try {
+                    wrapped.invokeExplosively(runnable);
+                } catch (RuntimeException | Error e) {
+                    throw e;
+                } catch (Throwable e) {
+                    throw new RuntimeException(e);
+                }
+            };
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     public Looper getLooper() {
         return mLooper;
     }
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index 57bcc04..c6dd29c 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -180,7 +180,6 @@
         "framework-minus-apex.ravenwood",
     ],
     static_libs: [
-        "core-xml-for-device",
         "hoststubgen-helper-libcore-runtime.ravenwood",
     ],
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 06eeb47c..1089f82 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -382,7 +382,7 @@
                     stubOutStream.putNextEntry(newEntry)
                     convertClass(classInternalName, /*forImpl=*/false, bis,
                             stubOutStream, filter, packageRedirector, enableChecker, classes,
-                            errors, stats)
+                            errors, null)
                     stubOutStream.closeEntry()
                 }
             }
@@ -415,7 +415,7 @@
             enableChecker: Boolean,
             classes: ClassNodes,
             errors: HostStubGenErrors,
-            stats: HostStubGenStats,
+            stats: HostStubGenStats?,
             ) {
         val cr = ClassReader(input)
 
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
index fe4072f..50518e1 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt
@@ -17,6 +17,7 @@
 
 import com.android.hoststubgen.asm.toHumanReadableClassName
 import com.android.hoststubgen.filters.FilterPolicyWithReason
+import org.objectweb.asm.Opcodes
 import java.io.PrintWriter
 
 open class HostStubGenStats {
@@ -28,12 +29,26 @@
 
     private val stats = mutableMapOf<String, Stats>()
 
-    fun onVisitPolicyForMethod(fullClassName: String, policy: FilterPolicyWithReason) {
+    fun onVisitPolicyForMethod(fullClassName: String, methodName: String, descriptor: String,
+                               policy: FilterPolicyWithReason, access: Int) {
+        // Ignore methods that aren't public
+        if ((access and Opcodes.ACC_PUBLIC) == 0) return
+        // Ignore methods that are abstract
+        if ((access and Opcodes.ACC_ABSTRACT) != 0) return
+        // Ignore methods where policy isn't relevant
         if (policy.isIgnoredForStats) return
 
         val packageName = resolvePackageName(fullClassName)
         val className = resolveClassName(fullClassName)
 
+        // Ignore methods for certain generated code
+        if (className.endsWith("Proto")
+                or className.endsWith("ProtoEnums")
+                or className.endsWith("LogTags")
+                or className.endsWith("StatsLog")) {
+            return
+        }
+
         val packageStats = stats.getOrPut(packageName) { Stats() }
         val classStats = packageStats.children.getOrPut(className) { Stats() }
 
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
index 53eb5a8..eb03f66 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
@@ -72,6 +72,6 @@
                     || reason.contains("is-enum")
                     || reason.contains("is-synthetic-method")
                     || reason.contains("special-class")
-                    || reason.contains("substitute-from")
+                    || reason.contains("substitute-to")
         }
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
index c20aa8b..45e140c 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -51,7 +51,7 @@
      */
     data class Options (
             val errors: HostStubGenErrors,
-            val stats: HostStubGenStats,
+            val stats: HostStubGenStats?,
             val enablePreTrace: Boolean,
             val enablePostTrace: Boolean,
             val enableNonStubMethodCallDetection: Boolean,
@@ -178,6 +178,7 @@
         }
         val p = filter.getPolicyForMethod(currentClassName, name, descriptor)
         log.d("visitMethod: %s%s [%x] [%s] Policy: %s", name, descriptor, access, signature, p)
+        options.stats?.onVisitPolicyForMethod(currentClassName, name, descriptor, p, access)
 
         log.withIndent {
             // If it's a substitute-from method, then skip (== remove).
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
index beca945..416b782 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
@@ -141,11 +141,6 @@
             substituted: Boolean,
             superVisitor: MethodVisitor?,
     ): MethodVisitor? {
-        // Record statistics about visiting this method when visible.
-        if ((access and Opcodes.ACC_PRIVATE) == 0) {
-            options.stats.onVisitPolicyForMethod(currentClassName, policy)
-        }
-
         // Inject method log, if needed.
         var innerVisitor = superVisitor