Merge "Remove the filter for "CtsContentTestCases" in postsubmit tests." 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/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/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 6883d18..aec464d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -241,6 +241,8 @@
         private static final long MAX_TIME_WINDOW_MS = 24 * HOUR_IN_MILLIS;
         private final JobScoreBucket[] mScoreBuckets = new JobScoreBucket[NUM_SCORE_BUCKETS];
         private int mScoreBucketIndex = 0;
+        private long mCachedScoreExpirationTimeElapsed;
+        private int mCachedScore;
 
         public void addScore(int add, long nowElapsed) {
             JobScoreBucket bucket = mScoreBuckets[mScoreBucketIndex];
@@ -248,10 +250,17 @@
                 bucket = new JobScoreBucket();
                 bucket.startTimeElapsed = nowElapsed;
                 mScoreBuckets[mScoreBucketIndex] = bucket;
+                // Brand new bucket, there's nothing to remove from the score,
+                // so just update the expiration time if needed.
+                mCachedScoreExpirationTimeElapsed = Math.min(mCachedScoreExpirationTimeElapsed,
+                        nowElapsed + MAX_TIME_WINDOW_MS);
             } else if (bucket.startTimeElapsed < nowElapsed - MAX_TIME_WINDOW_MS) {
                 // The bucket is too old.
                 bucket.reset();
                 bucket.startTimeElapsed = nowElapsed;
+                // Force a recalculation of the cached score instead of just updating the cached
+                // value and time in case there are multiple stale buckets.
+                mCachedScoreExpirationTimeElapsed = nowElapsed;
             } else if (bucket.startTimeElapsed
                     < nowElapsed - MAX_TIME_WINDOW_MS / NUM_SCORE_BUCKETS) {
                 // The current bucket's duration has completed. Move on to the next bucket.
@@ -261,16 +270,26 @@
             }
 
             bucket.score += add;
+            mCachedScore += add;
         }
 
         public int getScore(long nowElapsed) {
+            if (nowElapsed < mCachedScoreExpirationTimeElapsed) {
+                return mCachedScore;
+            }
             int score = 0;
             final long earliestElapsed = nowElapsed - MAX_TIME_WINDOW_MS;
+            long earliestValidBucketTimeElapsed = Long.MAX_VALUE;
             for (JobScoreBucket bucket : mScoreBuckets) {
                 if (bucket != null && bucket.startTimeElapsed >= earliestElapsed) {
                     score += bucket.score;
+                    if (earliestValidBucketTimeElapsed > bucket.startTimeElapsed) {
+                        earliestValidBucketTimeElapsed = bucket.startTimeElapsed;
+                    }
                 }
             }
+            mCachedScore = score;
+            mCachedScoreExpirationTimeElapsed = earliestValidBucketTimeElapsed + MAX_TIME_WINDOW_MS;
             return score;
         }
 
@@ -378,10 +397,16 @@
 
     @Override
     public void prepareForExecutionLocked(JobStatus jobStatus) {
+        if (jobStatus.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
+            // Don't include jobs for the TOP app in the score calculation.
+            return;
+        }
         // Use the job's requested priority to determine its score since that is what the developer
         // selected and it will be stable across job runs.
-        final int score = mFallbackFlexibilityDeadlineScores
-                .get(jobStatus.getJob().getPriority(), jobStatus.getJob().getPriority() / 100);
+        final int priority = jobStatus.getJob().getPriority();
+        final int score = mFallbackFlexibilityDeadlineScores.get(priority,
+                FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES
+                        .get(priority, priority / 100));
         JobScoreTracker jobScoreTracker =
                 mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName());
         if (jobScoreTracker == null) {
@@ -394,6 +419,10 @@
 
     @Override
     public void unprepareFromExecutionLocked(JobStatus jobStatus) {
+        if (jobStatus.lastEvaluatedBias == JobInfo.BIAS_TOP_APP) {
+            // Jobs for the TOP app are excluded from the score calculation.
+            return;
+        }
         // The job didn't actually start. Undo the score increase.
         JobScoreTracker jobScoreTracker =
                 mJobScoreTrackers.get(jobStatus.getSourceUid(), jobStatus.getSourcePackageName());
@@ -401,8 +430,10 @@
             Slog.e(TAG, "Unprepared a job that didn't result in a score change");
             return;
         }
-        final int score = mFallbackFlexibilityDeadlineScores
-                .get(jobStatus.getJob().getPriority(), jobStatus.getJob().getPriority() / 100);
+        final int priority = jobStatus.getJob().getPriority();
+        final int score = mFallbackFlexibilityDeadlineScores.get(priority,
+                FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_SCORES
+                        .get(priority, priority / 100));
         jobScoreTracker.addScore(-score, sElapsedRealtimeClock.millis());
     }
 
@@ -649,21 +680,24 @@
                     (long) Math.scalb(mRescheduledJobDeadline, js.getNumPreviousAttempts() - 2),
                     mMaxRescheduledDeadline);
         }
+
+        // Intentionally use the effective priority here. If a job's priority was effectively
+        // lowered, it will be less likely to run quickly given other policies in JobScheduler.
+        // Thus, there's no need to further delay the job based on flex policy.
+        final int jobPriority = js.getEffectivePriority();
+        final int jobScore =
+                getScoreLocked(js.getSourceUid(), js.getSourcePackageName(), nowElapsed);
+        // Set an upper limit on the fallback deadline so that the delay doesn't become extreme.
+        final long fallbackDurationMs = Math.min(3 * mFallbackFlexibilityDeadlineMs,
+                mFallbackFlexibilityDeadlines.get(jobPriority, mFallbackFlexibilityDeadlineMs)
+                        + mFallbackFlexibilityAdditionalScoreTimeFactors
+                                .get(jobPriority, MINUTE_IN_MILLIS) * jobScore);
+        final long fallbackDeadlineMs = earliest + fallbackDurationMs;
+
         if (js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME) {
-            // Intentionally use the effective priority here. If a job's priority was effectively
-            // lowered, it will be less likely to run quickly given other policies in JobScheduler.
-            // Thus, there's no need to further delay the job based on flex policy.
-            final int jobPriority = js.getEffectivePriority();
-            final int jobScore =
-                    getScoreLocked(js.getSourceUid(), js.getSourcePackageName(), nowElapsed);
-            // Set an upper limit on the fallback deadline so that the delay doesn't become extreme.
-            final long fallbackDeadlineMs = Math.min(3 * mFallbackFlexibilityDeadlineMs,
-                    mFallbackFlexibilityDeadlines.get(jobPriority, mFallbackFlexibilityDeadlineMs)
-                            + mFallbackFlexibilityAdditionalScoreTimeFactors
-                                    .get(jobPriority, MINUTE_IN_MILLIS) * jobScore);
-            return earliest + fallbackDeadlineMs;
+            return fallbackDeadlineMs;
         }
-        return js.getLatestRunTimeElapsed();
+        return Math.max(fallbackDeadlineMs, js.getLatestRunTimeElapsed());
     }
 
     @VisibleForTesting
@@ -976,7 +1010,8 @@
                     // Something has gone horribly wrong. This has only occurred on incorrectly
                     // configured tests, but add a check here for safety.
                     Slog.wtf(TAG, "Got invalid latest when scheduling alarm."
-                            + " Prefetch=" + js.getJob().isPrefetch());
+                            + " prefetch=" + js.getJob().isPrefetch()
+                            + " periodic=" + js.getJob().isPeriodic());
                     // Since things have gone wrong, the safest and most reliable thing to do is
                     // stop applying flex policy to the job.
                     mFlexibilityTracker.setNumDroppedFlexibleConstraints(js,
@@ -991,7 +1026,7 @@
 
                 if (DEBUG) {
                     Slog.d(TAG, "scheduleDropNumConstraintsAlarm: "
-                            + js.getSourcePackageName() + " " + js.getSourceUserId()
+                            + js.toShortString()
                             + " numApplied: " + js.getNumAppliedFlexibleConstraints()
                             + " numRequired: " + js.getNumRequiredFlexibleConstraints()
                             + " numSatisfied: " + Integer.bitCount(
@@ -1199,11 +1234,11 @@
             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
                     .put(PRIORITY_MAX, 0);
             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
-                    .put(PRIORITY_HIGH, 4 * MINUTE_IN_MILLIS);
+                    .put(PRIORITY_HIGH, 3 * MINUTE_IN_MILLIS);
             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
-                    .put(PRIORITY_DEFAULT, 3 * MINUTE_IN_MILLIS);
+                    .put(PRIORITY_DEFAULT, 2 * MINUTE_IN_MILLIS);
             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
-                    .put(PRIORITY_LOW, 2 * MINUTE_IN_MILLIS);
+                    .put(PRIORITY_LOW, 1 * MINUTE_IN_MILLIS);
             DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_ADDITIONAL_SCORE_TIME_FACTORS
                     .put(PRIORITY_MIN, 1 * MINUTE_IN_MILLIS);
             DEFAULT_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
@@ -1220,7 +1255,7 @@
 
         private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS;
         private static final long DEFAULT_RESCHEDULED_JOB_DEADLINE_MS = HOUR_IN_MILLIS;
-        private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = 5 * DAY_IN_MILLIS;
+        private static final long DEFAULT_MAX_RESCHEDULED_DEADLINE_MS = DAY_IN_MILLIS;
         @VisibleForTesting
         static final long DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = 3 * DAY_IN_MILLIS;
 
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 a3a686f..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
@@ -16,8 +16,6 @@
 
 package com.android.server.job.controllers;
 
-import static android.text.format.DateUtils.HOUR_IN_MILLIS;
-
 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
 import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
 import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
@@ -430,9 +428,6 @@
      */
     public static final int INTERNAL_FLAG_DEMOTED_BY_SYSTEM_UIJ = 1 << 2;
 
-    /** Minimum difference between start and end time to have flexible constraint */
-    @VisibleForTesting
-    static final long MIN_WINDOW_FOR_FLEXIBILITY_MS = HOUR_IN_MILLIS;
     /**
      * Versatile, persistable flags for a job that's updated within the system server,
      * as opposed to {@link JobInfo#flags} that's set by callers.
@@ -708,14 +703,10 @@
         final boolean lacksSomeFlexibleConstraints =
                 ((~requiredConstraints) & SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS) != 0
                         || mCanApplyTransportAffinities;
-        final boolean satisfiesMinWindowException =
-                (latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis)
-                >= MIN_WINDOW_FOR_FLEXIBILITY_MS;
 
         // The first time a job is rescheduled it will not be subject to flexible constraints.
         // Otherwise, every consecutive reschedule increases a jobs' flexibility deadline.
         if (!isRequestedExpeditedJob() && !job.isUserInitiated()
-                && satisfiesMinWindowException
                 && (numFailures + numSystemStops) != 1
                 && lacksSomeFlexibleConstraints) {
             requiredConstraints |= CONSTRAINT_FLEXIBLE;
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 0e42f80..0c504b6 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -7840,6 +7840,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
@@ -18846,6 +18847,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 +18899,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);
@@ -25869,6 +25872,9 @@
     method public int describeContents();
     method public int getErrorCode();
     method public int getFinalState();
+    method @NonNull public java.util.List<android.media.metrics.MediaItemInfo> getInputMediaItemInfos();
+    method public long getOperationTypes();
+    method @Nullable public android.media.metrics.MediaItemInfo getOutputMediaItemInfo();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.EditingEndedEvent> CREATOR;
     field public static final int ERROR_CODE_AUDIO_PROCESSING_FAILED = 18; // 0x12
@@ -25893,14 +25899,25 @@
     field public static final int FINAL_STATE_CANCELED = 2; // 0x2
     field public static final int FINAL_STATE_ERROR = 3; // 0x3
     field public static final int FINAL_STATE_SUCCEEDED = 1; // 0x1
+    field public static final long OPERATION_TYPE_AUDIO_EDIT = 8L; // 0x8L
+    field public static final long OPERATION_TYPE_AUDIO_TRANSCODE = 2L; // 0x2L
+    field public static final long OPERATION_TYPE_AUDIO_TRANSMUX = 32L; // 0x20L
+    field public static final long OPERATION_TYPE_PAUSED = 64L; // 0x40L
+    field public static final long OPERATION_TYPE_RESUMED = 128L; // 0x80L
+    field public static final long OPERATION_TYPE_VIDEO_EDIT = 4L; // 0x4L
+    field public static final long OPERATION_TYPE_VIDEO_TRANSCODE = 1L; // 0x1L
+    field public static final long OPERATION_TYPE_VIDEO_TRANSMUX = 16L; // 0x10L
     field public static final int TIME_SINCE_CREATED_UNKNOWN = -1; // 0xffffffff
   }
 
   @FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public static final class EditingEndedEvent.Builder {
     ctor public EditingEndedEvent.Builder(int);
+    method @NonNull public android.media.metrics.EditingEndedEvent.Builder addInputMediaItemInfo(@NonNull android.media.metrics.MediaItemInfo);
+    method @NonNull public android.media.metrics.EditingEndedEvent.Builder addOperationType(long);
     method @NonNull public android.media.metrics.EditingEndedEvent build();
     method @NonNull public android.media.metrics.EditingEndedEvent.Builder setErrorCode(int);
     method @NonNull public android.media.metrics.EditingEndedEvent.Builder setMetricsBundle(@NonNull android.os.Bundle);
+    method @NonNull public android.media.metrics.EditingEndedEvent.Builder setOutputMediaItemInfo(@NonNull android.media.metrics.MediaItemInfo);
     method @NonNull public android.media.metrics.EditingEndedEvent.Builder setTimeSinceCreatedMillis(@IntRange(from=android.media.metrics.EditingEndedEvent.TIME_SINCE_CREATED_UNKNOWN) long);
   }
 
@@ -25920,6 +25937,65 @@
     field @NonNull public static final android.media.metrics.LogSessionId LOG_SESSION_ID_NONE;
   }
 
+  @FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public final class MediaItemInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getAudioChannelCount();
+    method public long getAudioSampleCount();
+    method public int getAudioSampleRateHz();
+    method public long getClipDurationMillis();
+    method @NonNull public java.util.List<java.lang.String> getCodecNames();
+    method @Nullable public String getContainerMimeType();
+    method public long getDataTypes();
+    method public long getDurationMillis();
+    method @NonNull public java.util.List<java.lang.String> getSampleMimeTypes();
+    method public int getSourceType();
+    method public int getVideoDataSpace();
+    method public float getVideoFrameRate();
+    method public long getVideoSampleCount();
+    method @NonNull public android.util.Size getVideoSize();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.metrics.MediaItemInfo> CREATOR;
+    field public static final long DATA_TYPE_AUDIO = 4L; // 0x4L
+    field public static final long DATA_TYPE_CUE_POINTS = 128L; // 0x80L
+    field public static final long DATA_TYPE_DEPTH = 16L; // 0x10L
+    field public static final long DATA_TYPE_GAIN_MAP = 32L; // 0x20L
+    field public static final long DATA_TYPE_GAPLESS = 256L; // 0x100L
+    field public static final long DATA_TYPE_HIGH_DYNAMIC_RANGE_VIDEO = 1024L; // 0x400L
+    field public static final long DATA_TYPE_HIGH_FRAME_RATE = 64L; // 0x40L
+    field public static final long DATA_TYPE_IMAGE = 1L; // 0x1L
+    field public static final long DATA_TYPE_METADATA = 8L; // 0x8L
+    field public static final long DATA_TYPE_SPATIAL_AUDIO = 512L; // 0x200L
+    field public static final long DATA_TYPE_VIDEO = 2L; // 0x2L
+    field public static final int SOURCE_TYPE_CAMERA = 2; // 0x2
+    field public static final int SOURCE_TYPE_EDITING_SESSION = 3; // 0x3
+    field public static final int SOURCE_TYPE_GALLERY = 1; // 0x1
+    field public static final int SOURCE_TYPE_GENERATED = 7; // 0x7
+    field public static final int SOURCE_TYPE_LOCAL_FILE = 4; // 0x4
+    field public static final int SOURCE_TYPE_REMOTE_FILE = 5; // 0x5
+    field public static final int SOURCE_TYPE_REMOTE_LIVE_STREAM = 6; // 0x6
+    field public static final int SOURCE_TYPE_UNSPECIFIED = 0; // 0x0
+    field public static final int VALUE_UNSPECIFIED = -1; // 0xffffffff
+  }
+
+  @FlaggedApi("com.android.media.editing.flags.add_media_metrics_editing") public static final class MediaItemInfo.Builder {
+    ctor public MediaItemInfo.Builder();
+    method @NonNull public android.media.metrics.MediaItemInfo.Builder addCodecName(@NonNull String);
+    method @NonNull public android.media.metrics.MediaItemInfo.Builder addDataType(long);
+    method @NonNull public android.media.metrics.MediaItemInfo.Builder addSampleMimeType(@NonNull String);
+    method @NonNull public android.media.metrics.MediaItemInfo build();
+    method @NonNull public android.media.metrics.MediaItemInfo.Builder setAudioChannelCount(@IntRange(from=0) int);
+    method @NonNull public android.media.metrics.MediaItemInfo.Builder setAudioSampleCount(@IntRange(from=0) long);
+    method @NonNull public android.media.metrics.MediaItemInfo.Builder setAudioSampleRateHz(@IntRange(from=0) int);
+    method @NonNull public android.media.metrics.MediaItemInfo.Builder setClipDurationMillis(long);
+    method @NonNull public android.media.metrics.MediaItemInfo.Builder setContainerMimeType(@NonNull String);
+    method @NonNull public android.media.metrics.MediaItemInfo.Builder setDurationMillis(long);
+    method @NonNull public android.media.metrics.MediaItemInfo.Builder setSourceType(int);
+    method @NonNull public android.media.metrics.MediaItemInfo.Builder setVideoDataSpace(int);
+    method @NonNull public android.media.metrics.MediaItemInfo.Builder setVideoFrameRate(@FloatRange(from=0) float);
+    method @NonNull public android.media.metrics.MediaItemInfo.Builder setVideoSampleCount(@IntRange(from=0) long);
+    method @NonNull public android.media.metrics.MediaItemInfo.Builder setVideoSize(@NonNull android.util.Size);
+  }
+
   public final class MediaMetricsManager {
     method @NonNull public android.media.metrics.BundleSession createBundleSession();
     method @NonNull public android.media.metrics.EditingSession createEditingSession();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index ecbdeaa..0f1da41 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -1389,6 +1389,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 +2205,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);
   }
@@ -11064,6 +11066,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";
   }
 
@@ -12057,6 +12060,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();
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 19b265d..a7f80dd 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -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 {
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/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/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 0fbdbc4..ce03798 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";
 
     /**
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/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/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/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 3d70c5b..3b07f27 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;
@@ -1950,6 +1951,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 +2937,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 +3103,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..8b91bcb 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -62,3 +62,11 @@
     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
+}
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/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index c14fe57..78ce2d9 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -82,6 +82,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>
@@ -212,6 +219,11 @@
     <integer name="config_emergency_call_wait_for_connection_timeout_millis">20000</integer>
     <java-symbol type="integer" name="config_emergency_call_wait_for_connection_timeout_millis" />
 
+    <!-- Indicates the data limit in bytes that can be used for bootstrap sim until factory reset.
+         -1 means unlimited. -->
+    <integer name="config_esim_bootstrap_data_limit_bytes">-1</integer>
+    <java-symbol type="integer" name="config_esim_bootstrap_data_limit_bytes" />
+
     <!-- Telephony config for the PLMNs of all satellite providers. This is used by satellite modem
          to identify providers that should be ignored if the carrier config
          carrier_supported_satellite_services_per_provider_bundle does not support them.
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/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/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index e5045ae..70b2f21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -2959,13 +2959,17 @@
     }
 
     public void goToFullscreenFromSplit() {
-        boolean leftOrTop;
-        if (mSideStage.isFocused()) {
-            leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+        // If main stage is focused, toEnd = true if
+        // mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT. Otherwise toEnd = false
+        // If side stage is focused, toEnd = true if
+        // mSideStagePosition = SPLIT_POSITION_TOP_OR_LEFT. Otherwise toEnd = false
+        final boolean toEnd;
+        if (mMainStage.isFocused()) {
+            toEnd = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
         } else {
-            leftOrTop = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
+            toEnd = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
         }
-        mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
+        mSplitLayout.flingDividerToDismiss(toEnd, EXIT_REASON_FULLSCREEN_SHORTCUT);
     }
 
     /** Move the specified task to fullscreen, regardless of focus state. */
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/metrics/EditingEndedEvent.java b/media/java/android/media/metrics/EditingEndedEvent.java
index 5ed8d40..f1c5c9d 100644
--- a/media/java/android/media/metrics/EditingEndedEvent.java
+++ b/media/java/android/media/metrics/EditingEndedEvent.java
@@ -20,6 +20,7 @@
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
+import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Bundle;
@@ -27,6 +28,8 @@
 import android.os.Parcelable;
 
 import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 /** Event for an editing operation having ended. */
@@ -156,14 +159,66 @@
     @SuppressWarnings("HidingField") // Hiding field from superclass as for playback events.
     private final long mTimeSinceCreatedMillis;
 
+    private final ArrayList<MediaItemInfo> mInputMediaItemInfos;
+    @Nullable private final MediaItemInfo mOutputMediaItemInfo;
+
+    /** @hide */
+    @LongDef(
+            prefix = {"OPERATION_TYPE_"},
+            flag = true,
+            value = {
+                OPERATION_TYPE_VIDEO_TRANSCODE,
+                OPERATION_TYPE_AUDIO_TRANSCODE,
+                OPERATION_TYPE_VIDEO_EDIT,
+                OPERATION_TYPE_AUDIO_EDIT,
+                OPERATION_TYPE_VIDEO_TRANSMUX,
+                OPERATION_TYPE_AUDIO_TRANSMUX,
+                OPERATION_TYPE_PAUSED,
+                OPERATION_TYPE_RESUMED,
+            })
+    @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    public @interface OperationType {}
+
+    /** Input video was decoded and re-encoded. */
+    public static final long OPERATION_TYPE_VIDEO_TRANSCODE = 1;
+
+    /** Input audio was decoded and re-encoded. */
+    public static final long OPERATION_TYPE_AUDIO_TRANSCODE = 1L << 1;
+
+    /** Input video was edited. */
+    public static final long OPERATION_TYPE_VIDEO_EDIT = 1L << 2;
+
+    /** Input audio was edited. */
+    public static final long OPERATION_TYPE_AUDIO_EDIT = 1L << 3;
+
+    /** Input video samples were writted (muxed) directly to the output file without transcoding. */
+    public static final long OPERATION_TYPE_VIDEO_TRANSMUX = 1L << 4;
+
+    /** Input audio samples were written (muxed) directly to the output file without transcoding. */
+    public static final long OPERATION_TYPE_AUDIO_TRANSMUX = 1L << 5;
+
+    /** The editing operation was paused before it completed. */
+    public static final long OPERATION_TYPE_PAUSED = 1L << 6;
+
+    /** The editing operation resumed a previous (paused) operation. */
+    public static final long OPERATION_TYPE_RESUMED = 1L << 7;
+
+    private final @OperationType long mOperationTypes;
+
     private EditingEndedEvent(
             @FinalState int finalState,
             @ErrorCode int errorCode,
             long timeSinceCreatedMillis,
+            ArrayList<MediaItemInfo> inputMediaItemInfos,
+            @Nullable MediaItemInfo outputMediaItemInfo,
+            @OperationType long operationTypes,
             @NonNull Bundle extras) {
         mFinalState = finalState;
         mErrorCode = errorCode;
         mTimeSinceCreatedMillis = timeSinceCreatedMillis;
+        mInputMediaItemInfos = inputMediaItemInfos;
+        mOutputMediaItemInfo = outputMediaItemInfo;
+        mOperationTypes = operationTypes;
         mMetricsBundle = extras.deepCopy();
     }
 
@@ -194,6 +249,23 @@
         return mTimeSinceCreatedMillis;
     }
 
+    /** Gets information about the input media items, or an empty list if unspecified. */
+    @NonNull
+    public List<MediaItemInfo> getInputMediaItemInfos() {
+        return new ArrayList<>(mInputMediaItemInfos);
+    }
+
+    /** Gets information about the output media item, or {@code null} if unspecified. */
+    @Nullable
+    public MediaItemInfo getOutputMediaItemInfo() {
+        return mOutputMediaItemInfo;
+    }
+
+    /** Gets a set of flags describing the types of operations performed. */
+    public @OperationType long getOperationTypes() {
+        return mOperationTypes;
+    }
+
     /**
      * Gets metrics-related information that is not supported by dedicated methods.
      *
@@ -208,7 +280,7 @@
     @Override
     @NonNull
     public String toString() {
-        return "PlaybackErrorEvent { "
+        return "EditingEndedEvent { "
                 + "finalState = "
                 + mFinalState
                 + ", "
@@ -217,6 +289,15 @@
                 + ", "
                 + "timeSinceCreatedMillis = "
                 + mTimeSinceCreatedMillis
+                + ", "
+                + "inputMediaItemInfos = "
+                + mInputMediaItemInfos
+                + ", "
+                + "outputMediaItemInfo = "
+                + mOutputMediaItemInfo
+                + ", "
+                + "operationTypes = "
+                + mOperationTypes
                 + " }";
     }
 
@@ -227,12 +308,21 @@
         EditingEndedEvent that = (EditingEndedEvent) o;
         return mFinalState == that.mFinalState
                 && mErrorCode == that.mErrorCode
+                && Objects.equals(mInputMediaItemInfos, that.mInputMediaItemInfos)
+                && Objects.equals(mOutputMediaItemInfo, that.mOutputMediaItemInfo)
+                && mOperationTypes == that.mOperationTypes
                 && mTimeSinceCreatedMillis == that.mTimeSinceCreatedMillis;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mFinalState, mErrorCode, mTimeSinceCreatedMillis);
+        return Objects.hash(
+                mFinalState,
+                mErrorCode,
+                mInputMediaItemInfos,
+                mOutputMediaItemInfo,
+                mOperationTypes,
+                mTimeSinceCreatedMillis);
     }
 
     @Override
@@ -240,6 +330,9 @@
         dest.writeInt(mFinalState);
         dest.writeInt(mErrorCode);
         dest.writeLong(mTimeSinceCreatedMillis);
+        dest.writeTypedList(mInputMediaItemInfos);
+        dest.writeTypedObject(mOutputMediaItemInfo, /* parcelableFlags= */ 0);
+        dest.writeLong(mOperationTypes);
         dest.writeBundle(mMetricsBundle);
     }
 
@@ -249,15 +342,14 @@
     }
 
     private EditingEndedEvent(@NonNull Parcel in) {
-        int finalState = in.readInt();
-        int errorCode = in.readInt();
-        long timeSinceCreatedMillis = in.readLong();
-        Bundle metricsBundle = in.readBundle();
-
-        mFinalState = finalState;
-        mErrorCode = errorCode;
-        mTimeSinceCreatedMillis = timeSinceCreatedMillis;
-        mMetricsBundle = metricsBundle;
+        mFinalState = in.readInt();
+        mErrorCode = in.readInt();
+        mTimeSinceCreatedMillis = in.readLong();
+        mInputMediaItemInfos = new ArrayList<>();
+        in.readTypedList(mInputMediaItemInfos, MediaItemInfo.CREATOR);
+        mOutputMediaItemInfo = in.readTypedObject(MediaItemInfo.CREATOR);
+        mOperationTypes = in.readLong();
+        mMetricsBundle = in.readBundle();
     }
 
     public static final @NonNull Creator<EditingEndedEvent> CREATOR =
@@ -277,8 +369,11 @@
     @FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
     public static final class Builder {
         private final @FinalState int mFinalState;
+        private final ArrayList<MediaItemInfo> mInputMediaItemInfos;
         private @ErrorCode int mErrorCode;
         private long mTimeSinceCreatedMillis;
+        @Nullable private MediaItemInfo mOutputMediaItemInfo;
+        private @OperationType long mOperationTypes;
         private Bundle mMetricsBundle;
 
         /**
@@ -290,6 +385,7 @@
             mFinalState = finalState;
             mErrorCode = ERROR_CODE_NONE;
             mTimeSinceCreatedMillis = TIME_SINCE_CREATED_UNKNOWN;
+            mInputMediaItemInfos = new ArrayList<>();
             mMetricsBundle = new Bundle();
         }
 
@@ -312,20 +408,49 @@
             return this;
         }
 
+        /** Adds information about a media item that was input to the editing operation. */
+        public @NonNull Builder addInputMediaItemInfo(@NonNull MediaItemInfo mediaItemInfo) {
+            mInputMediaItemInfos.add(Objects.requireNonNull(mediaItemInfo));
+            return this;
+        }
+
+        /** Sets information about the output media item. */
+        public @NonNull Builder setOutputMediaItemInfo(@NonNull MediaItemInfo mediaItemInfo) {
+            mOutputMediaItemInfo = Objects.requireNonNull(mediaItemInfo);
+            return this;
+        }
+
+        /**
+         * Adds an operation type to the set of operations performed.
+         *
+         * @param operationType A type of operation performed as part of this editing operation.
+         */
+        public @NonNull Builder addOperationType(@OperationType long operationType) {
+            mOperationTypes |= operationType;
+            return this;
+        }
+
         /**
          * Sets metrics-related information that is not supported by dedicated methods.
          *
          * <p>Used for backwards compatibility by the metrics infrastructure.
          */
         public @NonNull Builder setMetricsBundle(@NonNull Bundle metricsBundle) {
-            mMetricsBundle = metricsBundle;
+            mMetricsBundle = Objects.requireNonNull(metricsBundle);
             return this;
         }
 
         /** Builds an instance. */
         public @NonNull EditingEndedEvent build() {
             return new EditingEndedEvent(
-                    mFinalState, mErrorCode, mTimeSinceCreatedMillis, mMetricsBundle);
+                    mFinalState,
+                    mErrorCode,
+                    mTimeSinceCreatedMillis,
+                    mInputMediaItemInfos,
+                    mOutputMediaItemInfo,
+                    mOperationTypes,
+                    mMetricsBundle);
         }
     }
+
 }
diff --git a/media/java/android/media/metrics/MediaItemInfo.java b/media/java/android/media/metrics/MediaItemInfo.java
new file mode 100644
index 0000000..63dd3cc
--- /dev/null
+++ b/media/java/android/media/metrics/MediaItemInfo.java
@@ -0,0 +1,565 @@
+/*
+ * 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.media.metrics;
+
+import static com.android.media.editing.flags.Flags.FLAG_ADD_MEDIA_METRICS_EDITING;
+
+import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.LongDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.hardware.DataSpace;
+import android.media.MediaCodec;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Size;
+
+import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** Represents information about a piece of media (for example, an audio or video file). */
+@FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
+public final class MediaItemInfo implements Parcelable {
+
+    /** @hide */
+    @IntDef(
+            prefix = {"SOURCE_TYPE_"},
+            value = {
+                SOURCE_TYPE_UNSPECIFIED,
+                SOURCE_TYPE_GALLERY,
+                SOURCE_TYPE_CAMERA,
+                SOURCE_TYPE_EDITING_SESSION,
+                SOURCE_TYPE_LOCAL_FILE,
+                SOURCE_TYPE_REMOTE_FILE,
+                SOURCE_TYPE_REMOTE_LIVE_STREAM,
+                SOURCE_TYPE_GENERATED,
+            })
+    @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    public @interface SourceType {}
+
+    /** The media item's source is not known. */
+    public static final int SOURCE_TYPE_UNSPECIFIED = 0;
+
+    /** The media item came from the device gallery. */
+    public static final int SOURCE_TYPE_GALLERY = 1;
+
+    /** The media item came directly from camera capture. */
+    public static final int SOURCE_TYPE_CAMERA = 2;
+
+    /** The media item was output by a previous editing session. */
+    public static final int SOURCE_TYPE_EDITING_SESSION = 3;
+
+    /** The media item is stored on the local device's file system. */
+    public static final int SOURCE_TYPE_LOCAL_FILE = 4;
+
+    /** The media item is a remote file (for example, it's loaded from an HTTP server). */
+    public static final int SOURCE_TYPE_REMOTE_FILE = 5;
+
+    /** The media item is a remotely-served live stream. */
+    public static final int SOURCE_TYPE_REMOTE_LIVE_STREAM = 6;
+
+    /** The media item was generated by another system. */
+    public static final int SOURCE_TYPE_GENERATED = 7;
+
+    /** @hide */
+    @LongDef(
+            prefix = {"DATA_TYPE_"},
+            flag = true,
+            value = {
+                DATA_TYPE_IMAGE,
+                DATA_TYPE_VIDEO,
+                DATA_TYPE_AUDIO,
+                DATA_TYPE_METADATA,
+                DATA_TYPE_DEPTH,
+                DATA_TYPE_GAIN_MAP,
+                DATA_TYPE_HIGH_FRAME_RATE,
+                DATA_TYPE_CUE_POINTS,
+                DATA_TYPE_GAPLESS,
+                DATA_TYPE_SPATIAL_AUDIO,
+                DATA_TYPE_HIGH_DYNAMIC_RANGE_VIDEO,
+            })
+    @Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    public @interface DataType {}
+
+    /** The media item includes image data. */
+    public static final long DATA_TYPE_IMAGE = 1L;
+
+    /** The media item includes video data. */
+    public static final long DATA_TYPE_VIDEO = 1L << 1;
+
+    /** The media item includes audio data. */
+    public static final long DATA_TYPE_AUDIO = 1L << 2;
+
+    /** The media item includes metadata. */
+    public static final long DATA_TYPE_METADATA = 1L << 3;
+
+    /** The media item includes depth (z-distance) information. */
+    public static final long DATA_TYPE_DEPTH = 1L << 4;
+
+    /** The media item includes gain map information (for example, an Ultra HDR gain map). */
+    public static final long DATA_TYPE_GAIN_MAP = 1L << 5;
+
+    /** The media item includes high frame rate video data. */
+    public static final long DATA_TYPE_HIGH_FRAME_RATE = 1L << 6;
+
+    /** The media item includes time-dependent speed setting metadata. */
+    public static final long DATA_TYPE_CUE_POINTS = 1L << 7;
+
+    /** The media item includes gapless audio metadata. */
+    public static final long DATA_TYPE_GAPLESS = 1L << 8;
+
+    /** The media item includes spatial audio data. */
+    public static final long DATA_TYPE_SPATIAL_AUDIO = 1L << 9;
+
+    /** The media item includes high dynamic range (HDR) video. */
+    public static final long DATA_TYPE_HIGH_DYNAMIC_RANGE_VIDEO = 1L << 10;
+
+    /** Special value for numerical fields where the value was not specified. */
+    public static final int VALUE_UNSPECIFIED = -1;
+
+    private final @SourceType int mSourceType;
+    private final @DataType long mDataTypes;
+    private final long mDurationMillis;
+    private final long mClipDurationMillis;
+    @Nullable private final String mContainerMimeType;
+    private final List<String> mSampleMimeTypes;
+    private final List<String> mCodecNames;
+    private final int mAudioSampleRateHz;
+    private final int mAudioChannelCount;
+    private final long mAudioSampleCount;
+    private final Size mVideoSize;
+    private final int mVideoDataSpace;
+    private final float mVideoFrameRate;
+    private final long mVideoSampleCount;
+
+    private MediaItemInfo(
+            @SourceType int sourceType,
+            @DataType long dataTypes,
+            long durationMillis,
+            long clipDurationMillis,
+            @Nullable String containerMimeType,
+            List<String> sampleMimeTypes,
+            List<String> codecNames,
+            int audioSampleRateHz,
+            int audioChannelCount,
+            long audioSampleCount,
+            Size videoSize,
+            int videoDataSpace,
+            float videoFrameRate,
+            long videoSampleCount) {
+        mSourceType = sourceType;
+        mDataTypes = dataTypes;
+        mDurationMillis = durationMillis;
+        mClipDurationMillis = clipDurationMillis;
+        mContainerMimeType = containerMimeType;
+        mSampleMimeTypes = sampleMimeTypes;
+        mCodecNames = codecNames;
+        mAudioSampleRateHz = audioSampleRateHz;
+        mAudioChannelCount = audioChannelCount;
+        mAudioSampleCount = audioSampleCount;
+        mVideoSize = videoSize;
+        mVideoDataSpace = videoDataSpace;
+        mVideoFrameRate = videoFrameRate;
+        mVideoSampleCount = videoSampleCount;
+    }
+
+    /**
+     * Returns where the media item came from, or {@link #SOURCE_TYPE_UNSPECIFIED} if not specified.
+     */
+    public @SourceType int getSourceType() {
+        return mSourceType;
+    }
+
+    /** Returns the data types that are present in the media item. */
+    public @DataType long getDataTypes() {
+        return mDataTypes;
+    }
+
+    /**
+     * Returns the duration of the media item, in milliseconds, or {@link #VALUE_UNSPECIFIED} if not
+     * specified.
+     */
+    public long getDurationMillis() {
+        return mDurationMillis;
+    }
+
+    /**
+     * Returns the duration of the clip taken from the media item, in milliseconds, or {@link
+     * #VALUE_UNSPECIFIED} if not specified.
+     */
+    public long getClipDurationMillis() {
+        return mClipDurationMillis;
+    }
+
+    /** Returns the MIME type of the media container, or {@code null} if unspecified. */
+    @Nullable
+    public String getContainerMimeType() {
+        return mContainerMimeType;
+    }
+
+    /**
+     * Returns the MIME types of samples stored in the media container, or an empty list if not
+     * known.
+     */
+    @NonNull
+    public List<String> getSampleMimeTypes() {
+        return new ArrayList<>(mSampleMimeTypes);
+    }
+
+    /**
+     * Returns the {@linkplain MediaCodec#getName() media codec names} for codecs that were used as
+     * part of encoding/decoding this media item, or an empty list if not known or not applicable.
+     */
+    @NonNull
+    public List<String> getCodecNames() {
+        return new ArrayList<>(mCodecNames);
+    }
+
+    /**
+     * Returns the sample rate of audio, in Hertz, or {@link #VALUE_UNSPECIFIED} if not specified.
+     */
+    public int getAudioSampleRateHz() {
+        return mAudioSampleRateHz;
+    }
+
+    /** Returns the number of audio channels, or {@link #VALUE_UNSPECIFIED} if not specified. */
+    public int getAudioChannelCount() {
+        return mAudioChannelCount;
+    }
+
+    /**
+     * Returns the number of audio frames in the item, after clipping (if applicable), or {@link
+     * #VALUE_UNSPECIFIED} if not specified.
+     */
+    public long getAudioSampleCount() {
+        return mAudioSampleCount;
+    }
+
+    /**
+     * Returns the video size, in pixels, or a {@link Size} with width and height set to {@link
+     * #VALUE_UNSPECIFIED} if not specified.
+     */
+    @NonNull
+    public Size getVideoSize() {
+        return mVideoSize;
+    }
+
+    /** Returns the {@linkplain DataSpace data space} for video, as a packed integer. */
+    @SuppressLint("MethodNameUnits") // Packed integer for an android.hardware.DataSpace.
+    public int getVideoDataSpace() {
+        return mVideoDataSpace;
+    }
+
+    /**
+     * Returns the average video frame rate, in frames per second, or {@link #VALUE_UNSPECIFIED} if
+     * not specified.
+     */
+    public float getVideoFrameRate() {
+        return mVideoFrameRate;
+    }
+
+    /**
+     * Returns the number of video frames, aftrer clipping (if applicable), or {@link
+     * #VALUE_UNSPECIFIED} if not specified.
+     */
+    public long getVideoSampleCount() {
+        return mVideoSampleCount;
+    }
+
+    /** Builder for {@link MediaItemInfo}. */
+    @FlaggedApi(FLAG_ADD_MEDIA_METRICS_EDITING)
+    public static final class Builder {
+
+        private @SourceType int mSourceType;
+        private @DataType long mDataTypes;
+        private long mDurationMillis;
+        private long mClipDurationMillis;
+        @Nullable private String mContainerMimeType;
+        private final ArrayList<String> mSampleMimeTypes;
+        private final ArrayList<String> mCodecNames;
+        private int mAudioSampleRateHz;
+        private int mAudioChannelCount;
+        private long mAudioSampleCount;
+        @Nullable private Size mVideoSize;
+        private int mVideoDataSpace;
+        private float mVideoFrameRate;
+        private long mVideoSampleCount;
+
+        /** Creates a new builder. */
+        public Builder() {
+            mSourceType = SOURCE_TYPE_UNSPECIFIED;
+            mDurationMillis = VALUE_UNSPECIFIED;
+            mClipDurationMillis = VALUE_UNSPECIFIED;
+            mSampleMimeTypes = new ArrayList<>();
+            mCodecNames = new ArrayList<>();
+            mAudioSampleRateHz = VALUE_UNSPECIFIED;
+            mAudioChannelCount = VALUE_UNSPECIFIED;
+            mAudioSampleCount = VALUE_UNSPECIFIED;
+            mVideoSize = new Size(VALUE_UNSPECIFIED, VALUE_UNSPECIFIED);
+            mVideoFrameRate = VALUE_UNSPECIFIED;
+            mVideoSampleCount = VALUE_UNSPECIFIED;
+        }
+
+        /** Sets where the media item came from. */
+        public @NonNull Builder setSourceType(@SourceType int sourceType) {
+            mSourceType = sourceType;
+            return this;
+        }
+
+        /** Adds an additional data type represented as part of the media item. */
+        public @NonNull Builder addDataType(@DataType long dataType) {
+            mDataTypes |= dataType;
+            return this;
+        }
+
+        /** Sets the duration of the media item, in milliseconds. */
+        public @NonNull Builder setDurationMillis(long durationMillis) {
+            mDurationMillis = durationMillis;
+            return this;
+        }
+
+        /** Sets the duration of the clip taken from the media item, in milliseconds. */
+        public @NonNull Builder setClipDurationMillis(long clipDurationMillis) {
+            mClipDurationMillis = clipDurationMillis;
+            return this;
+        }
+
+        /** Sets the MIME type of the media container. */
+        public @NonNull Builder setContainerMimeType(@NonNull String containerMimeType) {
+            mContainerMimeType = Objects.requireNonNull(containerMimeType);
+            return this;
+        }
+
+        /** Adds a sample MIME type stored in the media container. */
+        public @NonNull Builder addSampleMimeType(@NonNull String mimeType) {
+            mSampleMimeTypes.add(Objects.requireNonNull(mimeType));
+            return this;
+        }
+
+        /**
+         * Adds an {@linkplain MediaCodec#getName() media codec name} that was used as part of
+         * decoding/encoding this media item.
+         */
+        public @NonNull Builder addCodecName(@NonNull String codecName) {
+            mCodecNames.add(Objects.requireNonNull(codecName));
+            return this;
+        }
+
+        /** Sets the sample rate of audio, in Hertz. */
+        public @NonNull Builder setAudioSampleRateHz(@IntRange(from = 0) int audioSampleRateHz) {
+            mAudioSampleRateHz = audioSampleRateHz;
+            return this;
+        }
+
+        /** Sets the number of audio channels. */
+        public @NonNull Builder setAudioChannelCount(@IntRange(from = 0) int audioChannelCount) {
+            mAudioChannelCount = audioChannelCount;
+            return this;
+        }
+
+        /** Sets the number of audio frames in the item, after clipping (if applicable). */
+        public @NonNull Builder setAudioSampleCount(@IntRange(from = 0) long audioSampleCount) {
+            mAudioSampleCount = audioSampleCount;
+            return this;
+        }
+
+        /** Sets the video size, in pixels. */
+        public @NonNull Builder setVideoSize(@NonNull Size videoSize) {
+            mVideoSize = Objects.requireNonNull(videoSize);
+            return this;
+        }
+
+        /**
+         * Sets the {@link DataSpace} of video frames.
+         *
+         * @param videoDataSpace The data space, returned by {@link DataSpace#pack(int, int, int)}.
+         */
+        public @NonNull Builder setVideoDataSpace(int videoDataSpace) {
+            mVideoDataSpace = videoDataSpace;
+            return this;
+        }
+
+        /** Sets the average video frame rate, in frames per second. */
+        public @NonNull Builder setVideoFrameRate(@FloatRange(from = 0) float videoFrameRate) {
+            mVideoFrameRate = videoFrameRate;
+            return this;
+        }
+
+        /** Sets the number of video frames, after clipping (if applicable). */
+        public @NonNull Builder setVideoSampleCount(@IntRange(from = 0) long videoSampleCount) {
+            mVideoSampleCount = videoSampleCount;
+            return this;
+        }
+
+        /** Builds an instance. */
+        @NonNull
+        public MediaItemInfo build() {
+            return new MediaItemInfo(
+                    mSourceType,
+                    mDataTypes,
+                    mDurationMillis,
+                    mClipDurationMillis,
+                    mContainerMimeType,
+                    mSampleMimeTypes,
+                    mCodecNames,
+                    mAudioSampleRateHz,
+                    mAudioChannelCount,
+                    mAudioSampleCount,
+                    mVideoSize,
+                    mVideoDataSpace,
+                    mVideoFrameRate,
+                    mVideoSampleCount);
+        }
+    }
+
+    @Override
+    @NonNull
+    public String toString() {
+        return "MediaItemInfo { "
+                + "sourceType = "
+                + mSourceType
+                + ", "
+                + "dataTypes = "
+                + mDataTypes
+                + ", "
+                + "durationMillis = "
+                + mDurationMillis
+                + ", "
+                + "clipDurationMillis = "
+                + mClipDurationMillis
+                + ", "
+                + "containerMimeType = "
+                + mContainerMimeType
+                + ", "
+                + "sampleMimeTypes = "
+                + mSampleMimeTypes
+                + ", "
+                + "codecNames = "
+                + mCodecNames
+                + ", "
+                + "audioSampleRateHz = "
+                + mAudioSampleRateHz
+                + ", "
+                + "audioChannelCount = "
+                + mAudioChannelCount
+                + ", "
+                + "audioSampleCount = "
+                + mAudioSampleCount
+                + ", "
+                + "videoSize = "
+                + mVideoSize
+                + ", "
+                + "videoDataSpace = "
+                + mVideoDataSpace
+                + ", "
+                + "videoFrameRate = "
+                + mVideoFrameRate
+                + ", "
+                + "videoSampleCount = "
+                + mVideoSampleCount
+                + " }";
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        MediaItemInfo that = (MediaItemInfo) o;
+        return mSourceType == that.mSourceType
+                && mDataTypes == that.mDataTypes
+                && mDurationMillis == that.mDurationMillis
+                && mClipDurationMillis == that.mClipDurationMillis
+                && Objects.equals(mContainerMimeType, that.mContainerMimeType)
+                && mSampleMimeTypes.equals(that.mSampleMimeTypes)
+                && mCodecNames.equals(that.mCodecNames)
+                && mAudioSampleRateHz == that.mAudioSampleRateHz
+                && mAudioChannelCount == that.mAudioChannelCount
+                && mAudioSampleCount == that.mAudioSampleCount
+                && Objects.equals(mVideoSize, that.mVideoSize)
+                && Objects.equals(mVideoDataSpace, that.mVideoDataSpace)
+                && mVideoFrameRate == that.mVideoFrameRate
+                && mVideoSampleCount == that.mVideoSampleCount;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSourceType, mDataTypes);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mSourceType);
+        dest.writeLong(mDataTypes);
+        dest.writeLong(mDurationMillis);
+        dest.writeLong(mClipDurationMillis);
+        dest.writeString(mContainerMimeType);
+        dest.writeStringList(mSampleMimeTypes);
+        dest.writeStringList(mCodecNames);
+        dest.writeInt(mAudioSampleRateHz);
+        dest.writeInt(mAudioChannelCount);
+        dest.writeLong(mAudioSampleCount);
+        dest.writeInt(mVideoSize.getWidth());
+        dest.writeInt(mVideoSize.getHeight());
+        dest.writeInt(mVideoDataSpace);
+        dest.writeFloat(mVideoFrameRate);
+        dest.writeLong(mVideoSampleCount);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    private MediaItemInfo(@NonNull Parcel in) {
+        mSourceType = in.readInt();
+        mDataTypes = in.readLong();
+        mDurationMillis = in.readLong();
+        mClipDurationMillis = in.readLong();
+        mContainerMimeType = in.readString();
+        mSampleMimeTypes = new ArrayList<>();
+        in.readStringList(mSampleMimeTypes);
+        mCodecNames = new ArrayList<>();
+        in.readStringList(mCodecNames);
+        mAudioSampleRateHz = in.readInt();
+        mAudioChannelCount = in.readInt();
+        mAudioSampleCount = in.readLong();
+        int videoSizeWidth = in.readInt();
+        int videoSizeHeight = in.readInt();
+        mVideoSize = new Size(videoSizeWidth, videoSizeHeight);
+        mVideoDataSpace = in.readInt();
+        mVideoFrameRate = in.readFloat();
+        mVideoSampleCount = in.readLong();
+    }
+
+    public static final @NonNull Creator<MediaItemInfo> CREATOR =
+            new Creator<>() {
+                @Override
+                public MediaItemInfo[] newArray(int size) {
+                    return new MediaItemInfo[size];
+                }
+
+                @Override
+                public MediaItemInfo createFromParcel(@NonNull Parcel in) {
+                    return new MediaItemInfo(in);
+                }
+            };
+}
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/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index e3012cd..249fa7f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1621,6 +1621,7 @@
     }
 
     public static class AppEntry extends SizeInfo {
+        @VisibleForTesting String mProfileType;
         @Nullable public final File apkFile;
         public final long id;
         public String label;
@@ -1647,11 +1648,6 @@
          */
         public boolean isHomeApp;
 
-        /**
-         * Whether or not it's a cloned app .
-         */
-        public boolean isCloned;
-
         public String getNormalizedLabel() {
             if (normalizedLabel != null) {
                 return normalizedLabel;
@@ -1692,11 +1688,21 @@
                         () -> this.ensureLabelDescriptionLocked(context));
             }
             UserManager um = UserManager.get(context);
-            this.showInPersonalTab = shouldShowInPersonalTab(um, info.uid);
             UserInfo userInfo = um.getUserInfo(UserHandle.getUserId(info.uid));
-            if (userInfo != null) {
-                this.isCloned = userInfo.isCloneProfile();
-            }
+            mProfileType = userInfo.userType;
+            this.showInPersonalTab = shouldShowInPersonalTab(um, info.uid);
+        }
+
+        public boolean isClonedProfile() {
+            return UserManager.USER_TYPE_PROFILE_CLONE.equals(mProfileType);
+        }
+
+        public boolean isManagedProfile() {
+            return UserManager.USER_TYPE_PROFILE_MANAGED.equals(mProfileType);
+        }
+
+        public boolean isPrivateProfile() {
+            return UserManager.USER_TYPE_PROFILE_PRIVATE.equals(mProfileType);
         }
 
         /**
@@ -1890,16 +1896,24 @@
     };
 
     public static final AppFilter FILTER_WORK = new AppFilter() {
-        private int mCurrentUser;
 
         @Override
-        public void init() {
-            mCurrentUser = ActivityManager.getCurrentUser();
-        }
+        public void init() {}
 
         @Override
         public boolean filterApp(AppEntry entry) {
-            return !entry.showInPersonalTab;
+            return !entry.showInPersonalTab && entry.isManagedProfile();
+        }
+    };
+
+    public static final AppFilter FILTER_PRIVATE_PROFILE = new AppFilter() {
+
+        @Override
+        public void init() {}
+
+        @Override
+        public boolean filterApp(AppEntry entry) {
+            return !entry.showInPersonalTab && entry.isPrivateProfile();
         }
     };
 
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index c5598bf..213a66e 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.pm.ApplicationInfo;
+import android.os.UserManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -297,11 +298,26 @@
     @Test
     public void testPersonalAndWorkFiltersDisplaysCorrectApps() {
         mEntry.showInPersonalTab = true;
+        mEntry.mProfileType = UserManager.USER_TYPE_FULL_SYSTEM;
         assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isTrue();
         assertThat(ApplicationsState.FILTER_WORK.filterApp(mEntry)).isFalse();
 
         mEntry.showInPersonalTab = false;
+        mEntry.mProfileType = UserManager.USER_TYPE_PROFILE_MANAGED;
         assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isFalse();
         assertThat(ApplicationsState.FILTER_WORK.filterApp(mEntry)).isTrue();
     }
+
+    @Test
+    public void testPrivateProfileFilterDisplaysCorrectApps() {
+        mEntry.showInPersonalTab = true;
+        mEntry.mProfileType = UserManager.USER_TYPE_FULL_SYSTEM;
+        assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isTrue();
+        assertThat(ApplicationsState.FILTER_PRIVATE_PROFILE.filterApp(mEntry)).isFalse();
+
+        mEntry.showInPersonalTab = false;
+        mEntry.mProfileType = UserManager.USER_TYPE_PROFILE_PRIVATE;
+        assertThat(ApplicationsState.FILTER_PERSONAL.filterApp(mEntry)).isFalse();
+        assertThat(ApplicationsState.FILTER_PRIVATE_PROFILE.filterApp(mEntry)).isTrue();
+    }
 }
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/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/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/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/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/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/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/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/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/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/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..4293a27 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())
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/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/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index cec7a79..5d415c2 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -200,7 +200,9 @@
                 Slog.d(TAG, this + ": Starting");
             }
             mRunning = true;
-            updateBinding();
+            if (!Flags.enablePreventionOfKeepAliveRouteProviders()) {
+                updateBinding();
+            }
         }
         if (rebindIfDisconnected && mActiveConnection == null && shouldBind()) {
             unbind();
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index 233a3ab..fcca94b 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -150,7 +150,9 @@
                     mCallback.onAddProviderService(proxy);
                 } else if (sourceIndex >= targetIndex) {
                     MediaRoute2ProviderServiceProxy proxy = mProxies.get(sourceIndex);
-                    proxy.start(/* rebindIfDisconnected= */ true); // restart the proxy if needed
+                    proxy.start(
+                            /* rebindIfDisconnected= */
+                                    !Flags.enablePreventionOfKeepAliveRouteProviders());
                     Collections.swap(mProxies, sourceIndex, targetIndex++);
                 }
             }
diff --git a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
index bbe6d3a..2cd8fe0 100644
--- a/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
+++ b/services/core/java/com/android/server/media/metrics/MediaMetricsManagerService.java
@@ -18,10 +18,13 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.hardware.DataSpace;
 import android.media.MediaMetrics;
+import android.media.codec.Enums;
 import android.media.metrics.BundleSession;
 import android.media.metrics.EditingEndedEvent;
 import android.media.metrics.IMediaMetricsManager;
+import android.media.metrics.MediaItemInfo;
 import android.media.metrics.NetworkEvent;
 import android.media.metrics.PlaybackErrorEvent;
 import android.media.metrics.PlaybackMetrics;
@@ -31,7 +34,9 @@
 import android.os.PersistableBundle;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
+import android.text.TextUtils;
 import android.util.Base64;
+import android.util.Size;
 import android.util.Slog;
 import android.util.StatsEvent;
 import android.util.StatsLog;
@@ -72,7 +77,14 @@
     private static final String mMetricsId = MediaMetrics.Name.METRICS_MANAGER;
 
     private static final String FAILED_TO_GET = "failed_to_get";
+
+    private static final MediaItemInfo EMPTY_MEDIA_ITEM_INFO = new MediaItemInfo.Builder().build();
+    private static final int DURATION_BUCKETS_BELOW_ONE_MINUTE = 8;
+    private static final int DURATION_BUCKETS_COUNT = 13;
+    private static final String AUDIO_MIME_TYPE_PREFIX = "audio/";
+    private static final String VIDEO_MIME_TYPE_PREFIX = "video/";
     private final SecureRandom mSecureRandom;
+
     @GuardedBy("mLock")
     private Integer mMode = null;
     @GuardedBy("mLock")
@@ -353,6 +365,51 @@
             if (level == LOGGING_LEVEL_BLOCKED) {
                 return;
             }
+            MediaItemInfo inputMediaItemInfo =
+                    event.getInputMediaItemInfos().isEmpty()
+                            ? EMPTY_MEDIA_ITEM_INFO
+                            : event.getInputMediaItemInfos().get(0);
+            String inputAudioSampleMimeType =
+                    getFilteredFirstMimeType(
+                            inputMediaItemInfo.getSampleMimeTypes(), AUDIO_MIME_TYPE_PREFIX);
+            String inputVideoSampleMimeType =
+                    getFilteredFirstMimeType(
+                            inputMediaItemInfo.getSampleMimeTypes(), VIDEO_MIME_TYPE_PREFIX);
+            Size inputVideoSize = inputMediaItemInfo.getVideoSize();
+            int inputVideoResolution = getVideoResolutionEnum(inputVideoSize);
+            if (inputVideoResolution == Enums.RESOLUTION_UNKNOWN) {
+                // Try swapping width/height in case it's a portrait video.
+                inputVideoResolution =
+                        getVideoResolutionEnum(
+                                new Size(inputVideoSize.getHeight(), inputVideoSize.getWidth()));
+            }
+            List<String> inputCodecNames = inputMediaItemInfo.getCodecNames();
+            String inputFirstCodecName = !inputCodecNames.isEmpty() ? inputCodecNames.get(0) : "";
+            String inputSecondCodecName = inputCodecNames.size() > 1 ? inputCodecNames.get(1) : "";
+
+            MediaItemInfo outputMediaItemInfo =
+                    event.getOutputMediaItemInfo() == null
+                            ? EMPTY_MEDIA_ITEM_INFO
+                            : event.getOutputMediaItemInfo();
+            String outputAudioSampleMimeType =
+                    getFilteredFirstMimeType(
+                            outputMediaItemInfo.getSampleMimeTypes(), AUDIO_MIME_TYPE_PREFIX);
+            String outputVideoSampleMimeType =
+                    getFilteredFirstMimeType(
+                            outputMediaItemInfo.getSampleMimeTypes(), VIDEO_MIME_TYPE_PREFIX);
+            Size outputVideoSize = outputMediaItemInfo.getVideoSize();
+            int outputVideoResolution = getVideoResolutionEnum(outputVideoSize);
+            if (outputVideoResolution == Enums.RESOLUTION_UNKNOWN) {
+                // Try swapping width/height in case it's a portrait video.
+                outputVideoResolution =
+                        getVideoResolutionEnum(
+                                new Size(outputVideoSize.getHeight(), outputVideoSize.getWidth()));
+            }
+            List<String> outputCodecNames = outputMediaItemInfo.getCodecNames();
+            String outputFirstCodecName =
+                    !outputCodecNames.isEmpty() ? outputCodecNames.get(0) : "";
+            String outputSecondCodecName =
+                    outputCodecNames.size() > 1 ? outputCodecNames.get(1) : "";
             StatsEvent statsEvent =
                     StatsEvent.newBuilder()
                             .setAtomId(798)
@@ -360,6 +417,66 @@
                             .writeInt(event.getFinalState())
                             .writeInt(event.getErrorCode())
                             .writeLong(event.getTimeSinceCreatedMillis())
+                            .writeInt(getThroughputFps(event))
+                            .writeInt(event.getInputMediaItemInfos().size())
+                            .writeInt(inputMediaItemInfo.getSourceType())
+                            .writeLong(
+                                    getBucketedDurationMillis(
+                                            inputMediaItemInfo.getDurationMillis()))
+                            .writeLong(
+                                    getBucketedDurationMillis(
+                                            inputMediaItemInfo.getClipDurationMillis()))
+                            .writeString(
+                                    getFilteredMimeType(inputMediaItemInfo.getContainerMimeType()))
+                            .writeString(inputAudioSampleMimeType)
+                            .writeString(inputVideoSampleMimeType)
+                            .writeInt(getCodecEnum(inputVideoSampleMimeType))
+                            .writeInt(
+                                    getFilteredAudioSampleRateHz(
+                                            inputMediaItemInfo.getAudioSampleRateHz()))
+                            .writeInt(inputMediaItemInfo.getAudioChannelCount())
+                            .writeInt(inputVideoSize.getWidth())
+                            .writeInt(inputVideoSize.getHeight())
+                            .writeInt(inputVideoResolution)
+                            .writeInt(getVideoResolutionAspectRatioEnum(inputVideoSize))
+                            .writeInt(inputMediaItemInfo.getVideoDataSpace())
+                            .writeInt(
+                                    getVideoHdrFormatEnum(
+                                            inputMediaItemInfo.getVideoDataSpace(),
+                                            inputVideoSampleMimeType))
+                            .writeInt(Math.round(inputMediaItemInfo.getVideoFrameRate()))
+                            .writeInt(getVideoFrameRateEnum(inputMediaItemInfo.getVideoFrameRate()))
+                            .writeString(inputFirstCodecName)
+                            .writeString(inputSecondCodecName)
+                            .writeLong(
+                                    getBucketedDurationMillis(
+                                            outputMediaItemInfo.getDurationMillis()))
+                            .writeLong(
+                                    getBucketedDurationMillis(
+                                            outputMediaItemInfo.getClipDurationMillis()))
+                            .writeString(
+                                    getFilteredMimeType(outputMediaItemInfo.getContainerMimeType()))
+                            .writeString(outputAudioSampleMimeType)
+                            .writeString(outputVideoSampleMimeType)
+                            .writeInt(getCodecEnum(outputVideoSampleMimeType))
+                            .writeInt(
+                                    getFilteredAudioSampleRateHz(
+                                            outputMediaItemInfo.getAudioSampleRateHz()))
+                            .writeInt(outputMediaItemInfo.getAudioChannelCount())
+                            .writeInt(outputVideoSize.getWidth())
+                            .writeInt(outputVideoSize.getHeight())
+                            .writeInt(outputVideoResolution)
+                            .writeInt(getVideoResolutionAspectRatioEnum(outputVideoSize))
+                            .writeInt(outputMediaItemInfo.getVideoDataSpace())
+                            .writeInt(
+                                    getVideoHdrFormatEnum(
+                                            outputMediaItemInfo.getVideoDataSpace(),
+                                            outputVideoSampleMimeType))
+                            .writeInt(Math.round(outputMediaItemInfo.getVideoFrameRate()))
+                            .writeInt(
+                                    getVideoFrameRateEnum(outputMediaItemInfo.getVideoFrameRate()))
+                            .writeString(outputFirstCodecName)
+                            .writeString(outputSecondCodecName)
                             .usePooledBuffer()
                             .build();
             StatsLog.write(statsEvent);
@@ -511,4 +628,215 @@
             }
         }
     }
+
+    private static int getThroughputFps(EditingEndedEvent event) {
+        MediaItemInfo outputMediaItemInfo = event.getOutputMediaItemInfo();
+        if (outputMediaItemInfo == null) {
+            return -1;
+        }
+        long videoSampleCount = outputMediaItemInfo.getVideoSampleCount();
+        if (videoSampleCount == MediaItemInfo.VALUE_UNSPECIFIED) {
+            return -1;
+        }
+        long elapsedTimeMs = event.getTimeSinceCreatedMillis();
+        if (elapsedTimeMs == EditingEndedEvent.TIME_SINCE_CREATED_UNKNOWN) {
+            return -1;
+        }
+        return (int)
+                Math.min(Integer.MAX_VALUE, Math.round(1000.0 * videoSampleCount / elapsedTimeMs));
+    }
+
+    private static long getBucketedDurationMillis(long durationMillis) {
+        if (durationMillis == MediaItemInfo.VALUE_UNSPECIFIED || durationMillis <= 0) {
+            return -1;
+        }
+        // Bucket values in an exponential distribution to reduce the precision that's stored:
+        // bucket index -> range -> bucketed duration
+        // 1 -> [0, 469 ms) -> 235 ms
+        // 2 -> [469 ms, 938 ms) -> 469 ms
+        // 3 -> [938 ms, 1875 ms) -> 938 ms
+        // 4 -> [1875 ms, 3750 ms) -> 1875 ms
+        // 5 -> [3750 ms, 7500 ms) -> 3750 ms
+        // [...]
+        // 13 -> [960000 ms, max) -> 960000 ms
+        int bucketIndex =
+                (int)
+                        Math.floor(
+                                DURATION_BUCKETS_BELOW_ONE_MINUTE
+                                        + Math.log((durationMillis + 1) / 60_000.0) / Math.log(2));
+        // Clamp to range [0, DURATION_BUCKETS_COUNT].
+        bucketIndex = Math.min(DURATION_BUCKETS_COUNT, Math.max(0, bucketIndex));
+        // Map back onto the representative value for the bucket.
+        return (long)
+                Math.ceil(Math.pow(2, bucketIndex - DURATION_BUCKETS_BELOW_ONE_MINUTE) * 60_000.0);
+    }
+
+    /**
+     * Returns the first entry in {@code mimeTypes} with the given prefix, if it matches the
+     * filtering allowlist. If no entries match the prefix or if the first matching entry is not on
+     * the allowlist, returns an empty string.
+     */
+    private static String getFilteredFirstMimeType(List<String> mimeTypes, String prefix) {
+        int size = mimeTypes.size();
+        for (int i = 0; i < size; i++) {
+            String mimeType = mimeTypes.get(i);
+            if (mimeType.startsWith(prefix)) {
+                return getFilteredMimeType(mimeType);
+            }
+        }
+        return "";
+    }
+
+    private static String getFilteredMimeType(String mimeType) {
+        if (TextUtils.isEmpty(mimeType)) {
+            return "";
+        }
+        // Discard all inputs that aren't allowlisted MIME types.
+        return switch (mimeType) {
+            case "video/mp4",
+                            "video/x-matroska",
+                            "video/webm",
+                            "video/3gpp",
+                            "video/avc",
+                            "video/hevc",
+                            "video/x-vnd.on2.vp8",
+                            "video/x-vnd.on2.vp9",
+                            "video/av01",
+                            "video/mp2t",
+                            "video/mp4v-es",
+                            "video/mpeg",
+                            "video/x-flv",
+                            "video/dolby-vision",
+                            "video/raw",
+                            "audio/mp4",
+                            "audio/mp4a-latm",
+                            "audio/x-matroska",
+                            "audio/webm",
+                            "audio/mpeg",
+                            "audio/mpeg-L1",
+                            "audio/mpeg-L2",
+                            "audio/ac3",
+                            "audio/eac3",
+                            "audio/eac3-joc",
+                            "audio/av4",
+                            "audio/true-hd",
+                            "audio/vnd.dts",
+                            "audio/vnd.dts.hd",
+                            "audio/vorbis",
+                            "audio/opus",
+                            "audio/flac",
+                            "audio/ogg",
+                            "audio/wav",
+                            "audio/midi",
+                            "audio/raw",
+                            "application/mp4",
+                            "application/webm",
+                            "application/x-matroska",
+                            "application/dash+xml",
+                            "application/x-mpegURL",
+                            "application/vnd.ms-sstr+xml" ->
+                    mimeType;
+            default -> "";
+        };
+    }
+
+    private static int getCodecEnum(String mimeType) {
+        if (TextUtils.isEmpty(mimeType)) {
+            return Enums.CODEC_UNKNOWN;
+        }
+        return switch (mimeType) {
+            case "video/avc" -> Enums.CODEC_AVC;
+            case "video/hevc" -> Enums.CODEC_HEVC;
+            case "video/x-vnd.on2.vp8" -> Enums.CODEC_VP8;
+            case "video/x-vnd.on2.vp9" -> Enums.CODEC_VP9;
+            case "video/av01" -> Enums.CODEC_AV1;
+            default -> Enums.CODEC_UNKNOWN;
+        };
+    }
+
+    private static int getFilteredAudioSampleRateHz(int sampleRateHz) {
+        return switch (sampleRateHz) {
+            case 8000, 11025, 16000, 22050, 44100, 48000, 96000, 192000 -> sampleRateHz;
+            default -> -1;
+        };
+    }
+
+    private static int getVideoResolutionEnum(Size size) {
+        int width = size.getWidth();
+        int height = size.getHeight();
+        if (width == 352 && height == 640) {
+            return Enums.RESOLUTION_352X640;
+        } else if (width == 360 && height == 640) {
+            return Enums.RESOLUTION_360X640;
+        } else if (width == 480 && height == 640) {
+            return Enums.RESOLUTION_480X640;
+        } else if (width == 480 && height == 854) {
+            return Enums.RESOLUTION_480X854;
+        } else if (width == 540 && height == 960) {
+            return Enums.RESOLUTION_540X960;
+        } else if (width == 576 && height == 1024) {
+            return Enums.RESOLUTION_576X1024;
+        } else if (width == 1280 && height == 720) {
+            return Enums.RESOLUTION_720P_HD;
+        } else if (width == 1920 && height == 1080) {
+            return Enums.RESOLUTION_1080P_FHD;
+        } else if (width == 1440 && height == 2560) {
+            return Enums.RESOLUTION_1440X2560;
+        } else if (width == 3840 && height == 2160) {
+            return Enums.RESOLUTION_4K_UHD;
+        } else if (width == 7680 && height == 4320) {
+            return Enums.RESOLUTION_8K_UHD;
+        } else {
+            return Enums.RESOLUTION_UNKNOWN;
+        }
+    }
+
+    private static int getVideoResolutionAspectRatioEnum(Size size) {
+        int width = size.getWidth();
+        int height = size.getHeight();
+        if (width <= 0 || height <= 0) {
+            return android.media.editing.Enums.RESOLUTION_ASPECT_RATIO_UNSPECIFIED;
+        } else if (width < height) {
+            return android.media.editing.Enums.RESOLUTION_ASPECT_RATIO_PORTRAIT;
+        } else if (height < width) {
+            return android.media.editing.Enums.RESOLUTION_ASPECT_RATIO_LANDSCAPE;
+        } else {
+            return android.media.editing.Enums.RESOLUTION_ASPECT_RATIO_SQUARE;
+        }
+    }
+
+    private static int getVideoHdrFormatEnum(int dataSpace, String mimeType) {
+        if (dataSpace == DataSpace.DATASPACE_UNKNOWN) {
+            return Enums.HDR_FORMAT_UNKNOWN;
+        }
+        if (mimeType.equals("video/dolby-vision")) {
+            return Enums.HDR_FORMAT_DOLBY_VISION;
+        }
+        int standard = DataSpace.getStandard(dataSpace);
+        int transfer = DataSpace.getTransfer(dataSpace);
+        if (standard == DataSpace.STANDARD_BT2020 && transfer == DataSpace.TRANSFER_HLG) {
+            return Enums.HDR_FORMAT_HLG;
+        }
+        if (standard == DataSpace.STANDARD_BT2020 && transfer == DataSpace.TRANSFER_ST2084) {
+            // We don't currently distinguish HDR10+ from HDR10.
+            return Enums.HDR_FORMAT_HDR10;
+        }
+        return Enums.HDR_FORMAT_NONE;
+    }
+
+    private static int getVideoFrameRateEnum(float frameRate) {
+        int frameRateInt = Math.round(frameRate);
+        return switch (frameRateInt) {
+            case 24 -> Enums.FRAMERATE_24;
+            case 25 -> Enums.FRAMERATE_25;
+            case 30 -> Enums.FRAMERATE_30;
+            case 50 -> Enums.FRAMERATE_50;
+            case 60 -> Enums.FRAMERATE_60;
+            case 120 -> Enums.FRAMERATE_120;
+            case 240 -> Enums.FRAMERATE_240;
+            case 480 -> Enums.FRAMERATE_480;
+            case 960 -> Enums.FRAMERATE_960;
+            default -> Enums.FRAMERATE_UNKNOWN;
+        };
+    }
 }
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..3507d2d 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 {
@@ -11654,6 +11709,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 +11856,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 +12225,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/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/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/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 28471b3..6bcd778 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -49,7 +49,6 @@
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONTENT_TRIGGER;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
 import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
-import static com.android.server.job.controllers.JobStatus.MIN_WINDOW_FOR_FLEXIBILITY_MS;
 import static com.android.server.job.controllers.JobStatus.NO_LATEST_RUNTIME;
 
 import static org.junit.Assert.assertArrayEquals;
@@ -410,10 +409,12 @@
 
     @Test
     public void testOnConstantsUpdated_PercentsToDropConstraints() {
+        final long fallbackDuration = 12 * HOUR_IN_MILLIS;
         JobInfo.Builder jb = createJob(0)
-                .setOverrideDeadline(MIN_WINDOW_FOR_FLEXIBILITY_MS);
+                .setOverrideDeadline(HOUR_IN_MILLIS);
         JobStatus js = createJobStatus("testPercentsToDropConstraintsConfig", jb);
-        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
+        // Even though the override deadline is 1 hour, the fallback duration is still used.
+        assertEquals(FROZEN_TIME + fallbackDuration / 10 * 5,
                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
         setDeviceConfigString(KEY_PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS,
                 "500=1|2|3|4"
@@ -441,13 +442,13 @@
                 mFlexibilityController.mFcConfig.PERCENTS_TO_DROP_FLEXIBLE_CONSTRAINTS
                         .get(JobInfo.PRIORITY_MIN),
                 new int[]{54, 55, 56, 57});
-        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10,
+        assertEquals(FROZEN_TIME + fallbackDuration / 10,
                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
         js.setNumDroppedFlexibleConstraints(1);
-        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 2,
+        assertEquals(FROZEN_TIME + fallbackDuration / 10 * 2,
                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
         js.setNumDroppedFlexibleConstraints(2);
-        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 3,
+        assertEquals(FROZEN_TIME + fallbackDuration / 10 * 3,
                 mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js));
     }
 
@@ -504,37 +505,38 @@
 
     @Test
     public void testGetNextConstraintDropTimeElapsedLocked() {
+        final long fallbackDuration = 50 * HOUR_IN_MILLIS;
         setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 200 * HOUR_IN_MILLIS);
         setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES,
                 "500=" + HOUR_IN_MILLIS
                         + ",400=" + 25 * HOUR_IN_MILLIS
-                        + ",300=" + 50 * HOUR_IN_MILLIS
+                        + ",300=" + fallbackDuration
                         + ",200=" + 100 * HOUR_IN_MILLIS
                         + ",100=" + 200 * HOUR_IN_MILLIS);
 
         long nextTimeToDropNumConstraints;
 
         // no delay, deadline
-        JobInfo.Builder jb = createJob(0).setOverrideDeadline(MIN_WINDOW_FOR_FLEXIBILITY_MS);
+        JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
         JobStatus js = createJobStatus("time", jb);
 
         assertEquals(JobStatus.NO_EARLIEST_RUNTIME, js.getEarliestRunTime());
-        assertEquals(MIN_WINDOW_FOR_FLEXIBILITY_MS + FROZEN_TIME, js.getLatestRunTimeElapsed());
+        assertEquals(HOUR_IN_MILLIS + FROZEN_TIME, js.getLatestRunTimeElapsed());
         assertEquals(FROZEN_TIME, js.enqueueTime);
 
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
+        assertEquals(FROZEN_TIME + fallbackDuration / 10 * 5,
                 nextTimeToDropNumConstraints);
         js.setNumDroppedFlexibleConstraints(1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6,
+        assertEquals(FROZEN_TIME + fallbackDuration / 10 * 6,
                 nextTimeToDropNumConstraints);
         js.setNumDroppedFlexibleConstraints(2);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7,
+        assertEquals(FROZEN_TIME + fallbackDuration / 10 * 7,
                 nextTimeToDropNumConstraints);
 
         // delay, no deadline
@@ -574,81 +576,83 @@
 
         // delay, deadline
         jb = createJob(0)
-                .setOverrideDeadline(2 * MIN_WINDOW_FOR_FLEXIBILITY_MS)
-                .setMinimumLatency(MIN_WINDOW_FOR_FLEXIBILITY_MS);
+                .setOverrideDeadline(2 * HOUR_IN_MILLIS)
+                .setMinimumLatency(HOUR_IN_MILLIS);
         js = createJobStatus("time", jb);
 
-        final long windowStart = FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS;
+        final long windowStart = FROZEN_TIME + HOUR_IN_MILLIS;
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5,
+        assertEquals(windowStart + fallbackDuration / 10 * 5,
                 nextTimeToDropNumConstraints);
         js.setNumDroppedFlexibleConstraints(1);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6,
+        assertEquals(windowStart + fallbackDuration / 10 * 6,
                 nextTimeToDropNumConstraints);
         js.setNumDroppedFlexibleConstraints(2);
         nextTimeToDropNumConstraints = mFlexibilityController
                 .getNextConstraintDropTimeElapsedLocked(js);
-        assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7,
+        assertEquals(windowStart + fallbackDuration / 10 * 7,
                 nextTimeToDropNumConstraints);
     }
 
     @Test
     public void testCurPercent() {
+        final long fallbackDuration = 10 * HOUR_IN_MILLIS;
+        setDeviceConfigString(KEY_FALLBACK_FLEXIBILITY_DEADLINES, "300=" + fallbackDuration);
         long deadline = 100 * MINUTE_IN_MILLIS;
         long nowElapsed = FROZEN_TIME;
         JobInfo.Builder jb = createJob(0).setOverrideDeadline(deadline);
         JobStatus js = createJobStatus("time", jb);
 
         assertEquals(FROZEN_TIME, mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
-        assertEquals(deadline + FROZEN_TIME,
+        assertEquals(FROZEN_TIME + fallbackDuration,
                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, FROZEN_TIME));
-        nowElapsed = FROZEN_TIME + 60 * MINUTE_IN_MILLIS;
+        nowElapsed = FROZEN_TIME + 6 * HOUR_IN_MILLIS;
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
         assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
 
-        nowElapsed = FROZEN_TIME + 130 * MINUTE_IN_MILLIS;
+        nowElapsed = FROZEN_TIME + 13 * HOUR_IN_MILLIS;
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
         assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
 
-        nowElapsed = FROZEN_TIME + 95 * MINUTE_IN_MILLIS;
+        nowElapsed = FROZEN_TIME + 9 * HOUR_IN_MILLIS;
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
-        assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
+        assertEquals(90, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
 
         nowElapsed = FROZEN_TIME;
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
-        long delay = MINUTE_IN_MILLIS;
-        deadline = 101 * MINUTE_IN_MILLIS;
+        long delay = HOUR_IN_MILLIS;
+        deadline = HOUR_IN_MILLIS + 100 * MINUTE_IN_MILLIS;
         jb = createJob(0).setOverrideDeadline(deadline).setMinimumLatency(delay);
         js = createJobStatus("time", jb);
 
         assertEquals(FROZEN_TIME + delay,
                 mFlexibilityController.getLifeCycleBeginningElapsedLocked(js));
-        assertEquals(deadline + FROZEN_TIME,
+        assertEquals(FROZEN_TIME + delay + fallbackDuration,
                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed,
                         FROZEN_TIME + delay));
 
-        nowElapsed = FROZEN_TIME + delay + 60 * MINUTE_IN_MILLIS;
+        nowElapsed = FROZEN_TIME + delay + 6 * HOUR_IN_MILLIS;
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
 
         assertEquals(60, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
 
-        nowElapsed = FROZEN_TIME + 130 * MINUTE_IN_MILLIS;
+        nowElapsed = FROZEN_TIME + 13 * HOUR_IN_MILLIS;
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
         assertEquals(100, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
 
-        nowElapsed = FROZEN_TIME + delay + 95 * MINUTE_IN_MILLIS;
+        nowElapsed = FROZEN_TIME + delay + 9 * HOUR_IN_MILLIS;
         JobSchedulerService.sElapsedRealtimeClock =
                 Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC);
-        assertEquals(95, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
+        assertEquals(90, mFlexibilityController.getCurPercentOfLifecycleLocked(js, nowElapsed));
     }
 
     @Test
@@ -786,26 +790,27 @@
         // deadline
         JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
         JobStatus js = createJobStatus("time", jb);
-        assertEquals(HOUR_IN_MILLIS + FROZEN_TIME,
-                mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
+        assertEquals(3 * HOUR_IN_MILLIS + js.enqueueTime,
+                mFlexibilityController
+                        .getLifeCycleEndElapsedLocked(js, nowElapsed, js.enqueueTime));
 
         // no deadline
-        assertEquals(FROZEN_TIME + 2 * HOUR_IN_MILLIS,
+        assertEquals(js.enqueueTime + 2 * HOUR_IN_MILLIS,
                 mFlexibilityController.getLifeCycleEndElapsedLocked(
                         createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_HIGH)),
-                        nowElapsed, 100L));
-        assertEquals(FROZEN_TIME + 3 * HOUR_IN_MILLIS,
+                        nowElapsed, js.enqueueTime));
+        assertEquals(js.enqueueTime + 3 * HOUR_IN_MILLIS,
                 mFlexibilityController.getLifeCycleEndElapsedLocked(
                         createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)),
-                        nowElapsed, 100L));
-        assertEquals(FROZEN_TIME + 4 * HOUR_IN_MILLIS,
+                        nowElapsed, js.enqueueTime));
+        assertEquals(js.enqueueTime + 4 * HOUR_IN_MILLIS,
                 mFlexibilityController.getLifeCycleEndElapsedLocked(
                         createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_LOW)),
-                        nowElapsed, 100L));
-        assertEquals(FROZEN_TIME + 5 * HOUR_IN_MILLIS,
+                        nowElapsed, js.enqueueTime));
+        assertEquals(js.enqueueTime + 5 * HOUR_IN_MILLIS,
                 mFlexibilityController.getLifeCycleEndElapsedLocked(
                         createJobStatus("time", createJob(0).setPriority(JobInfo.PRIORITY_MIN)),
-                        nowElapsed, 100L));
+                        nowElapsed, js.enqueueTime));
     }
 
     @Test
@@ -871,14 +876,16 @@
         mFlexibilityController.prepareForExecutionLocked(jsLow);
         mFlexibilityController.prepareForExecutionLocked(jsMin);
 
-        // deadline
-        JobInfo.Builder jb = createJob(0).setOverrideDeadline(HOUR_IN_MILLIS);
-        JobStatus js = createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition", jb);
-        assertEquals(HOUR_IN_MILLIS + FROZEN_TIME,
-                mFlexibilityController.getLifeCycleEndElapsedLocked(js, nowElapsed, 0));
+        final long longDeadlineMs = 14 * 24 * HOUR_IN_MILLIS;
+        JobInfo.Builder jbWithLongDeadline = createJob(0).setOverrideDeadline(longDeadlineMs);
+        JobStatus jsWithLongDeadline = createJobStatus(
+                "testGetLifeCycleEndElapsedLocked_ScoreAddition", jbWithLongDeadline);
+        JobInfo.Builder jbWithShortDeadline =
+                createJob(0).setOverrideDeadline(15 * MINUTE_IN_MILLIS);
+        JobStatus jsWithShortDeadline = createJobStatus(
+                "testGetLifeCycleEndElapsedLocked_ScoreAddition", jbWithShortDeadline);
 
         final long earliestMs = 123L;
-        // no deadline
         assertEquals(earliestMs + HOUR_IN_MILLIS + 5 * 15 * MINUTE_IN_MILLIS,
                 mFlexibilityController.getLifeCycleEndElapsedLocked(
                         createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
@@ -894,6 +901,9 @@
                         createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
                                 createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT)),
                         nowElapsed, earliestMs));
+        assertEquals(earliestMs + HOUR_IN_MILLIS + 3 * 15 * MINUTE_IN_MILLIS,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(
+                        jsWithShortDeadline, nowElapsed, earliestMs));
         assertEquals(earliestMs + HOUR_IN_MILLIS + 2 * 15 * MINUTE_IN_MILLIS,
                 mFlexibilityController.getLifeCycleEndElapsedLocked(
                         createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
@@ -904,6 +914,9 @@
                         createJobStatus("testGetLifeCycleEndElapsedLocked_ScoreAddition",
                                 createJob(0).setPriority(JobInfo.PRIORITY_MIN)),
                         nowElapsed, earliestMs));
+        assertEquals(jsWithLongDeadline.enqueueTime + longDeadlineMs,
+                mFlexibilityController.getLifeCycleEndElapsedLocked(
+                        jsWithLongDeadline, nowElapsed, earliestMs));
     }
 
     @Test
@@ -1033,8 +1046,8 @@
         JobInfo.Builder jb = createJob(0);
         jb.setMinimumLatency(1);
         jb.setOverrideDeadline(2);
-        JobStatus js = createJobStatus("Disable Flexible When Job Has Short Window", jb);
-        assertFalse(js.hasFlexibilityConstraint());
+        JobStatus js = createJobStatus("testExceptions_ShortWindow", jb);
+        assertTrue(js.hasFlexibilityConstraint());
     }
 
     @Test
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..046e057 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -2545,6 +2545,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 +2588,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 +3007,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 +3250,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
@@ -5659,6 +5703,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
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index 3a0a6ab..e8ffe54 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -34,8 +34,20 @@
         "android.hardware.usb-V1.2-java",
         "android.hardware.usb-V1.3-java",
         "android.hardware.usb-V3-java",
+        "usb_flags_lib",
     ],
     lint: {
         baseline_filename: "lint-baseline.xml",
     },
 }
+
+aconfig_declarations {
+    name: "usb_flags",
+    package: "com.android.server.usb.flags",
+    srcs: ["**/usb_flags.aconfig"],
+}
+
+java_aconfig_library {
+    name: "usb_flags_lib",
+    aconfig_declarations: "usb_flags",
+}
diff --git a/services/usb/java/com/android/server/usb/UsbHandlerManager.java b/services/usb/java/com/android/server/usb/UsbHandlerManager.java
index f311274..d83ff1f 100644
--- a/services/usb/java/com/android/server/usb/UsbHandlerManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHandlerManager.java
@@ -37,7 +37,7 @@
  *
  * @hide
  */
-class UsbHandlerManager {
+public class UsbHandlerManager {
     private static final String LOG_TAG = UsbHandlerManager.class.getSimpleName();
 
     private final Context mContext;
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index f916660..2ff21ad 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -62,6 +63,7 @@
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.usb.flags.Flags;
 import com.android.server.utils.EventLogger;
 
 import libcore.io.IoUtils;
@@ -80,8 +82,20 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
-class UsbProfileGroupSettingsManager {
+public class UsbProfileGroupSettingsManager {
+    /**
+     * &lt;application&gt; level property that an app can specify to restrict any overlaying of
+     * activities when usb device is attached.
+     *
+     *
+     * <p>This should only be set by privileged apps having {@link Manifest.permission#MANAGE_USB}
+     * permission.
+     * @hide
+     */
+    public static final String PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES =
+            "android.app.PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES";
     private static final String TAG = UsbProfileGroupSettingsManager.class.getSimpleName();
     private static final boolean DEBUG = false;
 
@@ -101,6 +115,8 @@
 
     private final PackageManager mPackageManager;
 
+    private final ActivityManager mActivityManager;
+
     private final UserManager mUserManager;
     private final @NonNull UsbSettingsManager mSettingsManager;
 
@@ -224,7 +240,7 @@
      * @param settingsManager The settings manager of the service
      * @param usbResolveActivityManager The resovle activity manager of the service
      */
-    UsbProfileGroupSettingsManager(@NonNull Context context, @NonNull UserHandle user,
+    public UsbProfileGroupSettingsManager(@NonNull Context context, @NonNull UserHandle user,
             @NonNull UsbSettingsManager settingsManager,
             @NonNull UsbHandlerManager usbResolveActivityManager) {
         if (DEBUG) Slog.v(TAG, "Creating settings for " + user);
@@ -238,6 +254,7 @@
 
         mContext = context;
         mPackageManager = context.getPackageManager();
+        mActivityManager = context.getSystemService(ActivityManager.class);
         mSettingsManager = settingsManager;
         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
 
@@ -895,7 +912,10 @@
         // Send broadcast to running activities with registered intent
         mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
 
-        resolveActivity(intent, device, true /* showMtpNotification */);
+        //resolving activities only if there is no foreground activity restricting it.
+        if (!shouldRestrictOverlayActivities()) {
+            resolveActivity(intent, device, true /* showMtpNotification */);
+        }
     }
 
     private void resolveActivity(Intent intent, UsbDevice device, boolean showMtpNotification) {
@@ -918,6 +938,63 @@
         resolveActivity(intent, matches, defaultActivity, device, null);
     }
 
+    /**
+     * @return true if any application in foreground have set restrict_usb_overlay_activities as
+     * true in manifest file. The application needs to have MANAGE_USB permission.
+     */
+    private boolean shouldRestrictOverlayActivities() {
+
+        if (!Flags.allowRestrictionOfOverlayActivities()) return false;
+
+        List<ActivityManager.RunningAppProcessInfo> appProcessInfos = mActivityManager
+                .getRunningAppProcesses();
+
+        List<String> filteredAppProcessInfos = new ArrayList<>();
+        boolean shouldRestrictOverlayActivities;
+
+        //filtering out applications in foreground.
+        for (ActivityManager.RunningAppProcessInfo processInfo : appProcessInfos) {
+            if (processInfo.importance <= ActivityManager
+                    .RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+                filteredAppProcessInfos.addAll(List.of(processInfo.pkgList));
+            }
+        }
+
+        if (DEBUG) Slog.d(TAG, "packages in foreground : " + filteredAppProcessInfos);
+
+        List<String> packagesHoldingManageUsbPermission =
+                mPackageManager.getPackagesHoldingPermissions(
+                        new String[]{android.Manifest.permission.MANAGE_USB},
+                        PackageManager.MATCH_SYSTEM_ONLY).stream()
+                        .map(packageInfo -> packageInfo.packageName).collect(Collectors.toList());
+
+        //retaining only packages that hold the required permission
+        filteredAppProcessInfos.retainAll(packagesHoldingManageUsbPermission);
+
+        if (DEBUG) {
+            Slog.d(TAG, "packages in foreground with required permission : "
+                    + filteredAppProcessInfos);
+        }
+
+        shouldRestrictOverlayActivities = filteredAppProcessInfos.stream().anyMatch(pkg -> {
+            try {
+                return mPackageManager.getProperty(PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES, pkg)
+                        .getBoolean();
+            } catch (NameNotFoundException e) {
+                if (DEBUG) {
+                    Slog.d(TAG, "property PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES "
+                            + "not present for " + pkg);
+                }
+                return false;
+            }
+        });
+
+        if (shouldRestrictOverlayActivities) {
+            Slog.d(TAG, "restricting starting of usb overlay activities");
+        }
+        return shouldRestrictOverlayActivities;
+    }
+
     public void deviceAttachedForFixedHandler(UsbDevice device, ComponentName component) {
         final Intent intent = createDeviceAttachedIntent(device);
 
diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
index 8e53ff4..0b854a8 100644
--- a/services/usb/java/com/android/server/usb/UsbSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
@@ -33,7 +33,7 @@
 /**
  * Maintains all {@link UsbUserSettingsManager} for all users.
  */
-class UsbSettingsManager {
+public class UsbSettingsManager {
     private static final String LOG_TAG = UsbSettingsManager.class.getSimpleName();
     private static final boolean DEBUG = false;
 
@@ -70,7 +70,7 @@
      *
      * @return The settings for the user
      */
-    @NonNull UsbUserSettingsManager getSettingsForUser(@UserIdInt int userId) {
+    public @NonNull UsbUserSettingsManager getSettingsForUser(@UserIdInt int userId) {
         synchronized (mSettingsByUser) {
             UsbUserSettingsManager settings = mSettingsByUser.get(userId);
             if (settings == null) {
diff --git a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
index c2b8d01..be729c5 100644
--- a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
@@ -49,7 +49,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
-class UsbUserSettingsManager {
+public class UsbUserSettingsManager {
     private static final String TAG = UsbUserSettingsManager.class.getSimpleName();
     private static final boolean DEBUG = false;
 
@@ -81,7 +81,7 @@
      *
      * @return The resolve infos of the activities that can handle the intent
      */
-    List<ResolveInfo> queryIntentActivities(@NonNull Intent intent) {
+    public List<ResolveInfo> queryIntentActivities(@NonNull Intent intent) {
         return mPackageManager.queryIntentActivitiesAsUser(intent, PackageManager.GET_META_DATA,
                 mUser.getIdentifier());
     }
diff --git a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
new file mode 100644
index 0000000..ea6e502
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.usb.flags"
+
+flag {
+    name: "allow_restriction_of_overlay_activities"
+    namespace: "usb"
+    description: "This flag controls the restriction of usb overlay activities"
+    bug: "307231174"
+}
diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java
index 3c11da5..4ff9712 100644
--- a/telephony/java/android/telephony/DomainSelectionService.java
+++ b/telephony/java/android/telephony/DomainSelectionService.java
@@ -831,7 +831,7 @@
             @NonNull String tag, @NonNull String errorLogName) {
         try {
             CompletableFuture.runAsync(
-                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor).join();
+                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor);
         } catch (CancellationException | CompletionException e) {
             Rlog.w(tag, "Binder - " + errorLogName + " exception: " + e.getMessage());
         }
diff --git a/tests/UsbManagerTests/Android.bp b/tests/UsbManagerTests/Android.bp
index c02d8e9..70c7dad 100644
--- a/tests/UsbManagerTests/Android.bp
+++ b/tests/UsbManagerTests/Android.bp
@@ -29,12 +29,17 @@
     static_libs: [
         "frameworks-base-testutils",
         "androidx.test.rules",
-        "mockito-target-inline-minus-junit4",
+        "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "truth",
         "UsbManagerTestLib",
     ],
-    jni_libs: ["libdexmakerjvmtiagent"],
+    jni_libs: [
+        // Required for ExtendedMockito
+        "libdexmakerjvmtiagent",
+        "libmultiplejvmtiagentsinterferenceagent",
+        "libstaticjvmtiagent",
+    ],
     certificate: "platform",
     platform_apis: true,
     test_suites: ["device-tests"],
diff --git a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java
new file mode 100644
index 0000000..4780d8a
--- /dev/null
+++ b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usbtest;
+
+import static com.android.server.usb.UsbProfileGroupSettingsManager.PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.Property;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.hardware.usb.UsbDevice;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.usb.UsbHandlerManager;
+import com.android.server.usb.UsbProfileGroupSettingsManager;
+import com.android.server.usb.UsbSettingsManager;
+import com.android.server.usb.UsbUserSettingsManager;
+import com.android.server.usb.flags.Flags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link com.android.server.usb.UsbProfileGroupSettingsManager}.
+ * Note: MUST claim MANAGE_USB permission in Manifest
+ */
+@RunWith(AndroidJUnit4.class)
+public class UsbProfileGroupSettingsManagerTest {
+
+    private static final String TEST_PACKAGE_NAME = "testPkg";
+    @Mock
+    private Context mContext;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private ActivityManager mActivityManager;
+    @Mock
+    private UserHandle mUserHandle;
+    @Mock
+    private UsbSettingsManager mUsbSettingsManager;
+    @Mock
+    private UsbHandlerManager mUsbHandlerManager;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private UsbUserSettingsManager mUsbUserSettingsManager;
+    @Mock private Property mProperty;
+    private ActivityManager.RunningAppProcessInfo mRunningAppProcessInfo;
+    private PackageInfo mPackageInfo;
+    private UsbProfileGroupSettingsManager mUsbProfileGroupSettingsManager;
+    private MockitoSession mStaticMockSession;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mRunningAppProcessInfo = new ActivityManager.RunningAppProcessInfo();
+        mRunningAppProcessInfo.pkgList = new String[]{TEST_PACKAGE_NAME};
+        mPackageInfo = new PackageInfo();
+        mPackageInfo.packageName = TEST_PACKAGE_NAME;
+        mPackageInfo.applicationInfo = Mockito.mock(ApplicationInfo.class);
+
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager);
+        when(mContext.getResources()).thenReturn(Mockito.mock(Resources.class));
+        when(mContext.createPackageContextAsUser(anyString(), anyInt(), any(UserHandle.class)))
+                .thenReturn(mContext);
+        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+
+        mUsbProfileGroupSettingsManager = new UsbProfileGroupSettingsManager(mContext, mUserHandle,
+                mUsbSettingsManager, mUsbHandlerManager);
+
+        mStaticMockSession = ExtendedMockito.mockitoSession()
+                .mockStatic(Flags.class)
+                .strictness(Strictness.WARN)
+                .startMocking();
+
+        when(mPackageManager.getPackageInfo(TEST_PACKAGE_NAME, 0)).thenReturn(mPackageInfo);
+        when(mPackageManager.getProperty(eq(PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES),
+                eq(TEST_PACKAGE_NAME))).thenReturn(mProperty);
+        when(mUserManager.getEnabledProfiles(anyInt()))
+                .thenReturn(List.of(Mockito.mock(UserInfo.class)));
+        when(mUsbSettingsManager.getSettingsForUser(anyInt())).thenReturn(mUsbUserSettingsManager);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mStaticMockSession.finishMocking();
+    }
+
+    @Test
+    public void testDeviceAttached_flagTrueWithoutForegroundActivity_resolveActivityCalled() {
+        when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
+        when(mActivityManager.getRunningAppProcesses()).thenReturn(new ArrayList<>());
+        when(mPackageManager.getPackagesHoldingPermissions(
+                new String[]{android.Manifest.permission.MANAGE_USB},
+                PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo));
+        UsbDevice device = Mockito.mock(UsbDevice.class);
+        mUsbProfileGroupSettingsManager.deviceAttached(device);
+        verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
+    }
+
+    @Test
+    public void testDeviceAttached_noForegroundActivityWithUsbPermission_resolveActivityCalled() {
+        when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
+        when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
+        when(mPackageManager.getPackagesHoldingPermissions(
+                new String[]{android.Manifest.permission.MANAGE_USB},
+                PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(new ArrayList<>());
+        UsbDevice device = Mockito.mock(UsbDevice.class);
+        mUsbProfileGroupSettingsManager.deviceAttached(device);
+        verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
+    }
+
+    @Test
+    public void testDeviceAttached_foregroundActivityWithManifestField_resolveActivityNotCalled() {
+        when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
+        when(mProperty.getBoolean()).thenReturn(true);
+        when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
+        when(mPackageManager.getPackagesHoldingPermissions(
+                new String[]{android.Manifest.permission.MANAGE_USB},
+                PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo));
+        UsbDevice device = Mockito.mock(UsbDevice.class);
+        mUsbProfileGroupSettingsManager.deviceAttached(device);
+        verify(mUsbUserSettingsManager, times(0))
+                .queryIntentActivities(any(Intent.class));
+    }
+
+    @Test
+    public void testDeviceAttached_foregroundActivityWithoutManifestField_resolveActivityCalled() {
+        when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true);
+        when(mProperty.getBoolean()).thenReturn(false);
+        when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
+        when(mPackageManager.getPackagesHoldingPermissions(
+                new String[]{android.Manifest.permission.MANAGE_USB},
+                PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo));
+        UsbDevice device = Mockito.mock(UsbDevice.class);
+        mUsbProfileGroupSettingsManager.deviceAttached(device);
+        verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
+    }
+
+    @Test
+    public void testDeviceAttached_flagFalseForegroundActivity_resolveActivityCalled() {
+        when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(false);
+        when(mProperty.getBoolean()).thenReturn(true);
+        when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo));
+        when(mPackageManager.getPackagesHoldingPermissions(
+                new String[]{android.Manifest.permission.MANAGE_USB},
+                PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo));
+        UsbDevice device = Mockito.mock(UsbDevice.class);
+        mUsbProfileGroupSettingsManager.deviceAttached(device);
+        verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class));
+    }
+}