Merge "docs: Fixing bad summary fragment in Canvas.isOpaque()"
diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
index 5d67c96..50a7a19 100644
--- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
+++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
@@ -230,9 +230,10 @@
             android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
             android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
     @Override
-    public void stopUserVisibleJobsForUser(@NonNull String packageName, int userId) {
+    public void notePendingUserRequestedAppStop(@NonNull String packageName, int userId,
+            @Nullable String debugReason) {
         try {
-            mBinder.stopUserVisibleJobsForUser(packageName, userId);
+            mBinder.notePendingUserRequestedAppStop(packageName, userId, debugReason);
         } catch (RemoteException e) {
         }
     }
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
index a1f1954..1ea0c5b 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
@@ -48,5 +48,5 @@
     @EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
     void unregisterUserVisibleJobObserver(in IUserVisibleJobObserver observer);
     @EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
-    void stopUserVisibleJobsForUser(String packageName, int userId);
+    void notePendingUserRequestedAppStop(String packageName, int userId, String debugReason);
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index e0fffb4..d4110a3 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1897,7 +1897,11 @@
          * <p>
          * All user-initiated jobs must have an associated notification, set via
          * {@link JobService#setNotification(JobParameters, int, Notification, int)}, and will be
-         * shown in the Task Manager when running.
+         * shown in the Task Manager when running. These jobs cannot be rescheduled by the app
+         * if the user stops the job via system provided affordance (such as the Task Manager).
+         * Thus, it is best practice and recommended to provide action buttons in the
+         * associated notification to allow the user to stop the job gracefully
+         * and allow for rescheduling.
          *
          * <p>
          * If the app doesn't hold the {@link android.Manifest.permission#RUN_LONG_JOBS} permission
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 4aec484..3ba5c31 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -515,5 +515,6 @@
             android.Manifest.permission.MANAGE_ACTIVITY_TASKS,
             android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
     @SuppressWarnings("HiddenAbstractMethod")
-    public abstract void stopUserVisibleJobsForUser(@NonNull String packageName, int userId);
+    public abstract void notePendingUserRequestedAppStop(@NonNull String packageName, int userId,
+            @Nullable String debugReason);
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index 1a205d0..2ab4324 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -156,6 +156,12 @@
      * a future idle maintenance window.
      * </p>
      *
+     * <p class="note">
+     * Any {@link JobInfo.Builder#setUserInitiated(boolean) user-initiated job}
+     * cannot be rescheduled when the user has asked to stop the app
+     * via a system provided affordance (such as the Task Manager).
+     * In such situations, the value of {@code wantsReschedule} is always treated as {@code false}.
+     *
      * @param params The parameters identifying this job, as supplied to
      *               the job in the {@link #onStartJob(JobParameters)} callback.
      * @param wantsReschedule {@code true} if this job should be rescheduled according
@@ -220,6 +226,12 @@
      * Once this method returns (or times out), the system releases the wakelock that it is holding
      * on behalf of the job.</p>
      *
+     * <p class="note">
+     * Any {@link JobInfo.Builder#setUserInitiated(boolean) user-initiated job}
+     * cannot be rescheduled when stopped by the user via a system provided affordance (such as
+     * the Task Manager). In such situations, the returned value from this method call is always
+     * treated as {@code false}.
+     *
      * <p class="caution"><strong>Note:</strong> When a job is stopped and rescheduled via this
      * method call, the deadline constraint is excluded from the rescheduled job's constraint set.
      * The rescheduled job will run again once all remaining constraints are satisfied.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 62d97358..a474cc8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1282,17 +1282,20 @@
     }
 
     @GuardedBy("mLock")
-    void stopUserVisibleJobsLocked(int userId, @NonNull String packageName,
-            @JobParameters.StopReason int reason, int internalReasonCode) {
+    void markJobsForUserStopLocked(int userId, @NonNull String packageName,
+            @Nullable String debugReason) {
         for (int i = mActiveServices.size() - 1; i >= 0; --i) {
             final JobServiceContext jsc = mActiveServices.get(i);
             final JobStatus jobStatus = jsc.getRunningJobLocked();
 
-            if (jobStatus != null && userId == jobStatus.getSourceUserId()
-                    && jobStatus.getSourcePackageName().equals(packageName)
-                    && jobStatus.isUserVisibleJob()) {
-                jsc.cancelExecutingJobLocked(reason, internalReasonCode,
-                        JobParameters.getInternalReasonCodeDescription(internalReasonCode));
+            // Normally, we handle jobs primarily using the source package and userId,
+            // however, user-visible jobs are shown as coming from the calling app, so we
+            // need to operate on the jobs from that perspective here.
+            if (jobStatus != null && userId == jobStatus.getUserId()
+                    && jobStatus.getServiceComponent().getPackageName().equals(packageName)) {
+                jsc.markForProcessDeathLocked(JobParameters.STOP_REASON_USER,
+                        JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP,
+                        debugReason);
             }
         }
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index e8fcdd2..fce75a2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1701,16 +1701,15 @@
     }
 
     @VisibleForTesting
-    void stopUserVisibleJobsInternal(@NonNull String packageName, int userId) {
+    void notePendingUserRequestedAppStopInternal(@NonNull String packageName, int userId,
+            @Nullable String debugReason) {
         final int packageUid = mLocalPM.getPackageUid(packageName, 0, userId);
         if (packageUid < 0) {
             Slog.wtf(TAG, "Asked to stop jobs of an unknown package");
             return;
         }
         synchronized (mLock) {
-            mConcurrencyManager.stopUserVisibleJobsLocked(userId, packageName,
-                    JobParameters.STOP_REASON_USER,
-                    JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
+            mConcurrencyManager.markJobsForUserStopLocked(userId, packageName, debugReason);
             final ArraySet<JobStatus> jobs = mJobs.getJobsByUid(packageUid);
             for (int i = jobs.size() - 1; i >= 0; i--) {
                 final JobStatus job = jobs.valueAt(i);
@@ -2387,12 +2386,25 @@
      *
      * @param failureToReschedule Provided job status that we will reschedule.
      * @return A newly instantiated JobStatus with the same constraints as the last job except
-     * with adjusted timing constraints.
+     * with adjusted timing constraints, or {@code null} if the job shouldn't be rescheduled for
+     * some policy reason.
      * @see #maybeQueueReadyJobsForExecutionLocked
      */
+    @Nullable
     @VisibleForTesting
     JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule,
             @JobParameters.StopReason int stopReason, int internalStopReason) {
+        if (internalStopReason == JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP
+                && failureToReschedule.isUserVisibleJob()) {
+            // If a user stops an app via Task Manager and the job was user-visible, then assume
+            // the user wanted to stop that task and not let it run in the future. It's in the
+            // app's best interests to provide action buttons in their notification to avoid this
+            // scenario.
+            Slog.i(TAG,
+                    "Dropping " + failureToReschedule.toShortString() + " because of user stop");
+            return null;
+        }
+
         final long elapsedNowMillis = sElapsedRealtimeClock.millis();
         final JobInfo job = failureToReschedule.getJob();
 
@@ -4225,12 +4237,13 @@
 
         @Override
         @EnforcePermission(allOf = {MANAGE_ACTIVITY_TASKS, INTERACT_ACROSS_USERS_FULL})
-        public void stopUserVisibleJobsForUser(@NonNull String packageName, int userId) {
-            super.stopUserVisibleJobsForUser_enforcePermission();
+        public void notePendingUserRequestedAppStop(@NonNull String packageName, int userId,
+                @Nullable String debugReason) {
+            super.notePendingUserRequestedAppStop_enforcePermission();
             if (packageName == null) {
                 throw new NullPointerException("packageName");
             }
-            JobSchedulerService.this.stopUserVisibleJobsInternal(packageName, userId);
+            notePendingUserRequestedAppStopInternal(packageName, userId, debugReason);
         }
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 514d984..cc9e517 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -190,6 +190,14 @@
 
     private Network mPendingNetworkChange;
 
+    /**
+     * The reason this job is marked for death. If it's not marked for death,
+     * then the value should be {@link JobParameters#STOP_REASON_UNDEFINED}.
+     */
+    private int mDeathMarkStopReason = JobParameters.STOP_REASON_UNDEFINED;
+    private int mDeathMarkInternalStopReason;
+    private String mDeathMarkDebugReason;
+
     // Debugging: reason this job was last stopped.
     public String mStoppedReason;
 
@@ -452,6 +460,7 @@
             mStoppedReason = null;
             mStoppedTime = 0;
             job.startedAsExpeditedJob = job.shouldTreatAsExpeditedJob();
+            job.startedAsUserInitiatedJob = job.shouldTreatAsUserInitiatedJob();
             return true;
         }
     }
@@ -502,6 +511,34 @@
         doCancelLocked(reason, internalStopReason, debugReason);
     }
 
+    /**
+     * Called when an app's process is about to be killed and we want to update the job's stop
+     * reasons without telling the job it's going to be stopped.
+     */
+    @GuardedBy("mLock")
+    void markForProcessDeathLocked(@JobParameters.StopReason int reason,
+            int internalStopReason, @NonNull String debugReason) {
+        if (mVerb == VERB_FINISHED) {
+            if (DEBUG) {
+                Slog.d(TAG, "Too late to mark for death (verb=" + mVerb + "), ignoring.");
+            }
+            return;
+        }
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "Marking " + mRunningJob.toShortString() + " for death because "
+                            + reason + ":" + debugReason);
+        }
+        mDeathMarkStopReason = reason;
+        mDeathMarkInternalStopReason = internalStopReason;
+        mDeathMarkDebugReason = debugReason;
+        if (mParams.getStopReason() == JobParameters.STOP_REASON_UNDEFINED) {
+            // Only set the stop reason if we're not already trying to stop the job for some
+            // other reason in case that other stop is successful before the process dies.
+            mParams.setStopReason(reason, internalStopReason, debugReason);
+        }
+    }
+
     int getPreferredUid() {
         return mPreferredUid;
     }
@@ -754,6 +791,12 @@
     @Override
     public void onServiceDisconnected(ComponentName name) {
         synchronized (mLock) {
+            if (mDeathMarkStopReason != JobParameters.STOP_REASON_UNDEFINED) {
+                // Service "unexpectedly" disconnected, but we knew the process was going to die.
+                // Use that as the stop reason for logging/debugging purposes.
+                mParams.setStopReason(
+                        mDeathMarkStopReason, mDeathMarkInternalStopReason, mDeathMarkDebugReason);
+            }
             closeAndCleanupJobLocked(true /* needsReschedule */, "unexpectedly disconnected");
         }
     }
@@ -1182,29 +1225,51 @@
      * we want to clean up internally.
      */
     @GuardedBy("mLock")
-    private void closeAndCleanupJobLocked(boolean reschedule, @Nullable String reason) {
+    private void closeAndCleanupJobLocked(boolean reschedule, @Nullable String loggingDebugReason) {
         final JobStatus completedJob;
         if (mVerb == VERB_FINISHED) {
             return;
         }
         if (DEBUG) {
             Slog.d(TAG, "Cleaning up " + mRunningJob.toShortString()
-                    + " reschedule=" + reschedule + " reason=" + reason);
+                    + " reschedule=" + reschedule + " reason=" + loggingDebugReason);
         }
-        applyStoppedReasonLocked(reason);
+        applyStoppedReasonLocked(loggingDebugReason);
         completedJob = mRunningJob;
-        final int internalStopReason = mParams.getInternalStopReasonCode();
-        final int stopReason = mParams.getStopReason();
+        // Use the JobParameters stop reasons for logging and metric purposes,
+        // but if the job was marked for death, use that reason for rescheduling purposes.
+        // The discrepancy could happen if a job ends up stopping for some reason
+        // in the time between the job being marked and the process actually dying.
+        // Since the job stopped for another reason, we want to log the actual stop reason
+        // for the sake of accurate metrics and debugging,
+        // but we should use the death mark reasons when determining reschedule policy.
+        final int loggingStopReason = mParams.getStopReason();
+        final int loggingInternalStopReason = mParams.getInternalStopReasonCode();
+        final int reschedulingStopReason, reschedulingInternalStopReason;
+        if (mDeathMarkStopReason != JobParameters.STOP_REASON_UNDEFINED) {
+            if (DEBUG) {
+                Slog.d(TAG, "Job marked for death because of "
+                        + JobParameters.getInternalReasonCodeDescription(
+                                mDeathMarkInternalStopReason)
+                        + ": " + mDeathMarkDebugReason);
+            }
+            reschedulingStopReason = mDeathMarkStopReason;
+            reschedulingInternalStopReason = mDeathMarkInternalStopReason;
+        } else {
+            reschedulingStopReason = loggingStopReason;
+            reschedulingInternalStopReason = loggingInternalStopReason;
+        }
         mPreviousJobHadSuccessfulFinish =
-                (internalStopReason == JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
+                (loggingInternalStopReason == JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
         if (!mPreviousJobHadSuccessfulFinish) {
             mLastUnsuccessfulFinishElapsed = sElapsedRealtimeClock.millis();
         }
-        mJobPackageTracker.noteInactive(completedJob, internalStopReason, reason);
+        mJobPackageTracker.noteInactive(completedJob,
+                loggingInternalStopReason, loggingDebugReason);
         FrameworkStatsLog.write_non_chained(FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED,
                 completedJob.getSourceUid(), null, completedJob.getBatteryName(),
                 FrameworkStatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__FINISHED,
-                internalStopReason, completedJob.getStandbyBucket(), completedJob.getJobId(),
+                loggingInternalStopReason, completedJob.getStandbyBucket(), completedJob.getJobId(),
                 completedJob.hasChargingConstraint(),
                 completedJob.hasBatteryNotLowConstraint(),
                 completedJob.hasStorageNotLowConstraint(),
@@ -1215,7 +1280,7 @@
                 completedJob.hasContentTriggerConstraint(),
                 completedJob.isRequestedExpeditedJob(),
                 completedJob.startedAsExpeditedJob,
-                stopReason,
+                loggingStopReason,
                 completedJob.getJob().isPrefetch(),
                 completedJob.getJob().getPriority(),
                 completedJob.getEffectivePriority(),
@@ -1235,11 +1300,11 @@
         }
         try {
             mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(),
-                    internalStopReason);
+                    loggingInternalStopReason);
         } catch (RemoteException e) {
             // Whatever.
         }
-        if (mParams.getStopReason() == JobParameters.STOP_REASON_TIMEOUT) {
+        if (loggingStopReason == JobParameters.STOP_REASON_TIMEOUT) {
             mEconomyManagerInternal.noteInstantaneousEvent(
                     mRunningJob.getSourceUserId(), mRunningJob.getSourcePackageName(),
                     JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT,
@@ -1260,6 +1325,9 @@
         mCancelled = false;
         service = null;
         mAvailable = true;
+        mDeathMarkStopReason = JobParameters.STOP_REASON_UNDEFINED;
+        mDeathMarkInternalStopReason = 0;
+        mDeathMarkDebugReason = null;
         mPendingStopReason = JobParameters.STOP_REASON_UNDEFINED;
         mPendingInternalStopReason = 0;
         mPendingDebugStopReason = null;
@@ -1268,8 +1336,8 @@
         if (completedJob.isUserVisibleJob()) {
             mService.informObserversOfUserVisibleJobChange(this, completedJob, false);
         }
-        mCompletedListener.onJobCompletedLocked(completedJob, stopReason, internalStopReason,
-                reschedule);
+        mCompletedListener.onJobCompletedLocked(completedJob,
+                reschedulingStopReason, reschedulingInternalStopReason, reschedule);
         mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
     }
 
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 ce33a8e..1971a11 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
@@ -402,6 +402,11 @@
      * running. This isn't copied over when a job is rescheduled.
      */
     public boolean startedAsExpeditedJob = false;
+    /**
+     * Whether or not this particular JobStatus instance was treated as a user-initiated job
+     * when it started running. This isn't copied over when a job is rescheduled.
+     */
+    public boolean startedAsUserInitiatedJob = false;
 
     public boolean startedWithImmediacyPrivilege = false;
 
@@ -1407,7 +1412,7 @@
      * @return true if this is a job whose execution should be made visible to the user.
      */
     public boolean isUserVisibleJob() {
-        return shouldTreatAsUserInitiatedJob();
+        return shouldTreatAsUserInitiatedJob() || startedAsUserInitiatedJob;
     }
 
     /**
@@ -2568,6 +2573,13 @@
             pw.print(startedAsExpeditedJob);
             pw.println(")");
         }
+        if ((getFlags() & JobInfo.FLAG_USER_INITIATED) != 0) {
+            pw.print("userInitiatedApproved: ");
+            pw.print(shouldTreatAsUserInitiatedJob());
+            pw.print(" (started as UIJ: ");
+            pw.print(startedAsUserInitiatedJob);
+            pw.println(")");
+        }
         pw.decreaseIndent();
 
         if (changedAuthorities != null) {
diff --git a/core/api/current.txt b/core/api/current.txt
index dfc2602..f2ca78f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -121,6 +121,7 @@
     field public static final String INTERACT_ACROSS_PROFILES = "android.permission.INTERACT_ACROSS_PROFILES";
     field public static final String INTERNET = "android.permission.INTERNET";
     field public static final String KILL_BACKGROUND_PROCESSES = "android.permission.KILL_BACKGROUND_PROCESSES";
+    field public static final String LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE = "android.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE";
     field public static final String LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK = "android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK";
     field public static final String LOADER_USAGE_STATS = "android.permission.LOADER_USAGE_STATS";
     field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
@@ -7197,6 +7198,7 @@
   }
 
   public class StatusBarManager {
+    method @RequiresPermission(android.Manifest.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE) public boolean canLaunchCaptureContentActivityForNote(@NonNull android.app.Activity);
     method public void requestAddTileService(@NonNull android.content.ComponentName, @NonNull CharSequence, @NonNull android.graphics.drawable.Icon, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     field public static final int TILE_ADD_REQUEST_ERROR_APP_NOT_IN_FOREGROUND = 1004; // 0x3ec
     field public static final int TILE_ADD_REQUEST_ERROR_BAD_COMPONENT = 1002; // 0x3ea
@@ -10711,6 +10713,7 @@
     field public static final String ACTION_INSERT_OR_EDIT = "android.intent.action.INSERT_OR_EDIT";
     field public static final String ACTION_INSTALL_FAILURE = "android.intent.action.INSTALL_FAILURE";
     field @Deprecated public static final String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE";
+    field @RequiresPermission(android.Manifest.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE) public static final String ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE = "android.intent.action.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE";
     field public static final String ACTION_LOCALE_CHANGED = "android.intent.action.LOCALE_CHANGED";
     field public static final String ACTION_LOCKED_BOOT_COMPLETED = "android.intent.action.LOCKED_BOOT_COMPLETED";
     field public static final String ACTION_MAIN = "android.intent.action.MAIN";
@@ -10804,6 +10807,11 @@
     field public static final String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
     field @Deprecated public static final String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
     field public static final String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
+    field public static final int CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN = 4; // 0x4
+    field public static final int CAPTURE_CONTENT_FOR_NOTE_FAILED = 1; // 0x1
+    field public static final int CAPTURE_CONTENT_FOR_NOTE_SUCCESS = 0; // 0x0
+    field public static final int CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED = 2; // 0x2
+    field public static final int CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED = 3; // 0x3
     field public static final String CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET = "android.intent.category.ACCESSIBILITY_SHORTCUT_TARGET";
     field public static final String CATEGORY_ALTERNATIVE = "android.intent.category.ALTERNATIVE";
     field public static final String CATEGORY_APP_BROWSER = "android.intent.category.APP_BROWSER";
@@ -10859,6 +10867,7 @@
     field public static final String EXTRA_AUTO_LAUNCH_SINGLE_CHOICE = "android.intent.extra.AUTO_LAUNCH_SINGLE_CHOICE";
     field public static final String EXTRA_BCC = "android.intent.extra.BCC";
     field public static final String EXTRA_BUG_REPORT = "android.intent.extra.BUG_REPORT";
+    field public static final String EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE = "android.intent.extra.CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE";
     field public static final String EXTRA_CC = "android.intent.extra.CC";
     field @Deprecated public static final String EXTRA_CHANGED_COMPONENT_NAME = "android.intent.extra.changed_component_name";
     field public static final String EXTRA_CHANGED_COMPONENT_NAME_LIST = "android.intent.extra.changed_component_name_list";
@@ -13500,6 +13509,18 @@
     method public void unregisterCredentialDescription(@NonNull android.credentials.UnregisterCredentialDescriptionRequest);
   }
 
+  public final class CredentialOption implements android.os.Parcelable {
+    ctor public CredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
+    method public int describeContents();
+    method @NonNull public android.os.Bundle getCandidateQueryData();
+    method @NonNull public android.os.Bundle getCredentialRetrievalData();
+    method @NonNull public String getType();
+    method public boolean isSystemProviderRequired();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CredentialOption> CREATOR;
+    field public static final String FLATTENED_REQUEST = "android.credentials.GetCredentialOption.FLATTENED_REQUEST_STRING";
+  }
+
   public class GetCredentialException extends java.lang.Exception {
     ctor public GetCredentialException(@NonNull String, @Nullable String);
     ctor public GetCredentialException(@NonNull String, @Nullable String, @Nullable Throwable);
@@ -13512,31 +13533,19 @@
     field @NonNull public static final String TYPE_USER_CANCELED = "android.credentials.GetCredentialException.TYPE_USER_CANCELED";
   }
 
-  public final class GetCredentialOption implements android.os.Parcelable {
-    ctor public GetCredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
-    method public int describeContents();
-    method @NonNull public android.os.Bundle getCandidateQueryData();
-    method @NonNull public android.os.Bundle getCredentialRetrievalData();
-    method @NonNull public String getType();
-    method public boolean isSystemProviderRequired();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialOption> CREATOR;
-    field public static final String FLATTENED_REQUEST = "android.credentials.GetCredentialOption.FLATTENED_REQUEST_STRING";
-  }
-
   public final class GetCredentialRequest implements android.os.Parcelable {
     method public int describeContents();
+    method @NonNull public java.util.List<android.credentials.CredentialOption> getCredentialOptions();
     method @NonNull public android.os.Bundle getData();
-    method @NonNull public java.util.List<android.credentials.GetCredentialOption> getGetCredentialOptions();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialRequest> CREATOR;
   }
 
   public static final class GetCredentialRequest.Builder {
     ctor public GetCredentialRequest.Builder(@NonNull android.os.Bundle);
-    method @NonNull public android.credentials.GetCredentialRequest.Builder addGetCredentialOption(@NonNull android.credentials.GetCredentialOption);
+    method @NonNull public android.credentials.GetCredentialRequest.Builder addCredentialOption(@NonNull android.credentials.CredentialOption);
     method @NonNull public android.credentials.GetCredentialRequest build();
-    method @NonNull public android.credentials.GetCredentialRequest.Builder setGetCredentialOptions(@NonNull java.util.List<android.credentials.GetCredentialOption>);
+    method @NonNull public android.credentials.GetCredentialRequest.Builder setCredentialOptions(@NonNull java.util.List<android.credentials.CredentialOption>);
   }
 
   public final class GetCredentialResponse implements android.os.Parcelable {
@@ -40286,10 +40295,10 @@
   }
 
   public final class GetCredentialRequest implements android.os.Parcelable {
-    ctor public GetCredentialRequest(@NonNull android.service.credentials.CallingAppInfo, @NonNull android.credentials.GetCredentialOption);
+    ctor public GetCredentialRequest(@NonNull android.service.credentials.CallingAppInfo, @NonNull android.credentials.CredentialOption);
     method public int describeContents();
     method @NonNull public android.service.credentials.CallingAppInfo getCallingAppInfo();
-    method @NonNull public android.credentials.GetCredentialOption getGetCredentialOption();
+    method @NonNull public android.credentials.CredentialOption getGetCredentialOption();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.GetCredentialRequest> CREATOR;
   }
@@ -60323,7 +60332,7 @@
     method @UiThread public void remove();
   }
 
-  public class SurfaceSyncGroup {
+  public final class SurfaceSyncGroup {
     ctor public SurfaceSyncGroup(@NonNull String);
     method @UiThread public boolean add(@Nullable android.view.AttachedSurfaceControl, @Nullable Runnable);
     method public boolean add(@NonNull android.view.SurfaceControlViewHost.SurfacePackage, @Nullable Runnable);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fe3605f..0476d79 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2119,6 +2119,13 @@
     method protected void finalize();
     method public void notifyEvent(@NonNull android.app.search.Query, @NonNull android.app.search.SearchTargetEvent);
     method @Nullable public void query(@NonNull android.app.search.Query, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.search.SearchTarget>>);
+    method public void registerEmptyQueryResultUpdateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.app.search.SearchSession.Callback);
+    method public void requestEmptyQueryResultUpdate();
+    method public void unregisterEmptyQueryResultUpdateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.app.search.SearchSession.Callback);
+  }
+
+  public static interface SearchSession.Callback {
+    method public void onTargetsAvailable(@NonNull java.util.List<android.app.search.SearchTarget>);
   }
 
   public final class SearchSessionId implements android.os.Parcelable {
@@ -12268,7 +12275,11 @@
     method @MainThread public abstract void onDestroy(@NonNull android.app.search.SearchSessionId);
     method @MainThread public abstract void onNotifyEvent(@NonNull android.app.search.SearchSessionId, @NonNull android.app.search.Query, @NonNull android.app.search.SearchTargetEvent);
     method @MainThread public abstract void onQuery(@NonNull android.app.search.SearchSessionId, @NonNull android.app.search.Query, @NonNull java.util.function.Consumer<java.util.List<android.app.search.SearchTarget>>);
+    method @MainThread public void onRequestEmptyQueryResultUpdate(@NonNull android.app.search.SearchSessionId);
     method public void onSearchSessionCreated(@NonNull android.app.search.SearchContext, @NonNull android.app.search.SearchSessionId);
+    method @MainThread public void onStartUpdateEmptyQueryResult();
+    method @MainThread public void onStopUpdateEmptyQueryResult();
+    method public final void updateEmptyQueryResult(@NonNull android.app.search.SearchSessionId, @NonNull java.util.List<android.app.search.SearchTarget>);
   }
 
 }
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 62a1eb9..34afd8a 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2409,6 +2409,8 @@
     field public static final String DISABLE_WINDOW_BLURS = "disable_window_blurs";
     field public static final String DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD = "dynamic_power_savings_disable_threshold";
     field public static final String DYNAMIC_POWER_SAVINGS_ENABLED = "dynamic_power_savings_enabled";
+    field public static final String HDR_CONVERSION_MODE = "hdr_conversion_mode";
+    field public static final String HDR_FORCE_CONVERSION_TYPE = "hdr_force_conversion_type";
     field public static final String HIDDEN_API_BLACKLIST_EXEMPTIONS = "hidden_api_blacklist_exemptions";
     field public static final String HIDDEN_API_POLICY = "hidden_api_policy";
     field public static final String HIDE_ERROR_DIALOGS = "hide_error_dialogs";
@@ -3146,6 +3148,9 @@
 
   public final class SurfaceControl implements android.os.Parcelable {
     ctor public SurfaceControl(@NonNull android.view.SurfaceControl, @NonNull String);
+    method @NonNull public android.view.Choreographer getChoreographer();
+    method @NonNull public android.view.Choreographer getChoreographer(@NonNull android.os.Looper);
+    method public boolean hasChoreographer();
     method public boolean isSameSurface(@NonNull android.view.SurfaceControl);
   }
 
diff --git a/core/api/test-lint-baseline.txt b/core/api/test-lint-baseline.txt
index e849cdbc..4a97280 100644
--- a/core/api/test-lint-baseline.txt
+++ b/core/api/test-lint-baseline.txt
@@ -855,6 +855,10 @@
     New setting keys are not allowed (Field: DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD); use getters/setters in relevant manager class
 NoSettingsProvider: android.provider.Settings.Global#DYNAMIC_POWER_SAVINGS_ENABLED:
     New setting keys are not allowed (Field: DYNAMIC_POWER_SAVINGS_ENABLED); use getters/setters in relevant manager class
+NoSettingsProvider: android.provider.Settings.Global#HDR_CONVERSION_MODE:
+    New setting keys are not allowed (Field: HDR_CONVERSION_MODE); use getters/setters in relevant manager class
+NoSettingsProvider: android.provider.Settings.Global#HDR_FORCE_CONVERSION_TYPE:
+    New setting keys are not allowed (Field: HDR_FORCE_CONVERSION_TYPE); use getters/setters in relevant manager class
 NoSettingsProvider: android.provider.Settings.Global#HIDDEN_API_BLACKLIST_EXEMPTIONS:
     New setting keys are not allowed (Field: HIDDEN_API_BLACKLIST_EXEMPTIONS); use getters/setters in relevant manager class
 NoSettingsProvider: android.provider.Settings.Global#HIDDEN_API_POLICY:
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index 20d19c1..d8eb03e 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -109,7 +109,7 @@
      */
     // TODO (b/254661666): Change to @EnabledAfter(T)
     @ChangeId
-    @Disabled
+    @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
     @Overridable
     public static final long FGS_TYPE_NONE_DISABLED_CHANGE_ID = 255038118L;
 
@@ -144,7 +144,7 @@
      */
     // TODO (b/254661666): Change to @EnabledAfter(T)
     @ChangeId
-    @Disabled
+    @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
     @Overridable
     public static final long FGS_TYPE_PERMISSION_CHANGE_ID = 254662522L;
 
@@ -1059,7 +1059,7 @@
             if (policy.isTypeDisabled(callerUid)) {
                 return FGS_TYPE_POLICY_CHECK_DISABLED;
             }
-            int permissionResult = PERMISSION_DENIED;
+            int permissionResult = PERMISSION_GRANTED;
             // Do we have the permission to start FGS with this type.
             if (policy.mAllOfPermissions != null) {
                 permissionResult = policy.mAllOfPermissions.checkPermissions(context,
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index e485397..a155457 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1140,6 +1140,22 @@
      * Callback called on timeout for {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
      * See {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE} for more details.
      *
+     * <p>If the foreground service of type
+     * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}
+     * doesn't finish even after it's timed out,
+     * the app will be declared an ANR after a short grace period of several seconds.
+     *
+     * <p>Note, even though
+     * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}
+     * was added
+     * on Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * it can be also used on
+     * on prior android versions (just like other new foreground service types can be used).
+     * However, because {@link android.app.Service#onTimeout(int)} did not exist on prior versions,
+     * it will never called on such versions.
+     * Because of this, developers must make sure to stop the foreground service even if
+     * {@link android.app.Service#onTimeout(int)} is not called on such versions.
+     *
      * @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when
      * the service started.
      */
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 1c1a558..9e31011 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -30,6 +30,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.graphics.drawable.Icon;
 import android.media.INearbyMediaDevicesProvider;
 import android.media.INearbyMediaDevicesUpdateCallback;
@@ -47,6 +48,7 @@
 import android.util.Slog;
 import android.view.View;
 
+import com.android.internal.statusbar.AppClipsServiceConnector;
 import com.android.internal.statusbar.IAddTileResultCallback;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.IUndoMediaTransferCallback;
@@ -1190,6 +1192,37 @@
         return CompatChanges.isChangeEnabled(MEDIA_CONTROL_SESSION_ACTIONS, packageName, user);
     }
 
+    /**
+     * Checks whether the supplied activity can {@link Activity#startActivityForResult(Intent, int)}
+     * a system activity that captures content on the screen to take a screenshot.
+     *
+     * <p>Note: The result should not be cached.
+     *
+     * <p>The system activity displays an editing tool that allows user to edit the screenshot, save
+     * it on device, and return the edited screenshot as {@link android.net.Uri} to the calling
+     * activity. User interaction is required to return the edited screenshot to the calling
+     * activity.
+     *
+     * <p>When {@code true}, callers can use {@link Activity#startActivityForResult(Intent, int)}
+     * to start start the content capture activity using
+     * {@link Intent#ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE}.
+     *
+     * @param activity Calling activity
+     * @return true if the activity supports launching the capture content activity for note.
+     *
+     * @see Intent#ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE
+     * @see Manifest.permission#LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE
+     * @see android.app.role.RoleManager#ROLE_NOTES
+     */
+    @RequiresPermission(Manifest.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE)
+    public boolean canLaunchCaptureContentActivityForNote(@NonNull Activity activity) {
+        Objects.requireNonNull(activity);
+        IBinder activityToken = activity.getActivityToken();
+        int taskId = ActivityClient.getInstance().getTaskForActivity(activityToken, false);
+        return new AppClipsServiceConnector(mContext)
+                .canLaunchCaptureContentActivityForNote(taskId);
+    }
+
     /** @hide */
     public static String windowStateToString(int state) {
         if (state == WINDOW_STATE_HIDING) return "WINDOW_STATE_HIDING";
diff --git a/core/java/android/app/search/ISearchUiManager.aidl b/core/java/android/app/search/ISearchUiManager.aidl
index a298a2c..fefbd5a 100644
--- a/core/java/android/app/search/ISearchUiManager.aidl
+++ b/core/java/android/app/search/ISearchUiManager.aidl
@@ -36,5 +36,11 @@
 
     void notifyEvent(in SearchSessionId sessionId, in Query input, in SearchTargetEvent event);
 
+    void registerEmptyQueryResultUpdateCallback(in SearchSessionId sessionId, in ISearchCallback callback);
+
+    void requestEmptyQueryResultUpdate(in SearchSessionId sessionId);
+
+    void unregisterEmptyQueryResultUpdateCallback(in SearchSessionId sessionId, in ISearchCallback callback);
+
     void destroySearchSession(in SearchSessionId sessionId);
 }
diff --git a/core/java/android/app/search/SearchSession.java b/core/java/android/app/search/SearchSession.java
index 10db337..eda68dd 100644
--- a/core/java/android/app/search/SearchSession.java
+++ b/core/java/android/app/search/SearchSession.java
@@ -28,8 +28,11 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import dalvik.system.CloseGuard;
 
 import java.util.List;
@@ -83,6 +86,8 @@
 
     private final SearchSessionId mSessionId;
     private final IBinder mToken = new Binder();
+    @GuardedBy("itself")
+    private final ArrayMap<Callback, CallbackWrapper> mRegisteredCallbacks = new ArrayMap<>();
 
     /**
      * Creates a new search ui client.
@@ -156,6 +161,95 @@
             e.rethrowFromSystemServer();
         }
     }
+    /**
+     * Request the search ui service provide continuous updates of {@link SearchTarget} list
+     * via the provided callback to render for zero state, until the given callback is
+     * unregistered. Zero state means when user entered search ui but not issued any query yet.
+     *
+     * @see SearchSession.Callback#onTargetsAvailable(List).
+     *
+     * @param callbackExecutor The callback executor to use when calling the callback.
+     * @param callback The Callback to be called when updates of search targets for zero state
+     *                 are available.
+     */
+    public void registerEmptyQueryResultUpdateCallback(
+            @NonNull @CallbackExecutor Executor callbackExecutor,
+            @NonNull Callback callback) {
+        synchronized (mRegisteredCallbacks) {
+            if (mIsClosed.get()) {
+                throw new IllegalStateException("This client has already been destroyed.");
+            }
+            if (mRegisteredCallbacks.containsKey(callback)) {
+                // Skip if this callback is already registered
+                return;
+            }
+            try {
+                final CallbackWrapper callbackWrapper = new CallbackWrapper(callbackExecutor,
+                        callback::onTargetsAvailable);
+                mInterface.registerEmptyQueryResultUpdateCallback(mSessionId, callbackWrapper);
+                mRegisteredCallbacks.put(callback, callbackWrapper);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to register for empty query result updates", e);
+                e.rethrowAsRuntimeException();
+            }
+        }
+    }
+
+    /**
+     * Requests the search ui service to stop providing continuous updates of {@link SearchTarget}
+     * to the provided callback for zero state until the callback is re-registered. Zero state
+     * means when user entered search ui but not issued any query yet.
+     *
+     * @see {@link SearchSession#registerEmptyQueryResultUpdateCallback(Executor, Callback)}
+     * @param callback The callback to be unregistered.
+     */
+    public void unregisterEmptyQueryResultUpdateCallback(
+            @NonNull @CallbackExecutor Executor callbackExecutor,
+            @NonNull Callback callback) {
+        synchronized (mRegisteredCallbacks) {
+            if (mIsClosed.get()) {
+                throw new IllegalStateException("This client has already been destroyed.");
+            }
+
+            if (!mRegisteredCallbacks.containsKey(callback)) {
+                // Skip if this callback was never registered
+                return;
+            }
+            try {
+                final CallbackWrapper callbackWrapper = mRegisteredCallbacks.remove(callback);
+                mInterface.unregisterEmptyQueryResultUpdateCallback(mSessionId, callbackWrapper);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to unregister for empty query result updates", e);
+                e.rethrowAsRuntimeException();
+            }
+        }
+    }
+
+    /**
+     * Requests the search ui service to dispatch a new set of search targets to the pre-registered
+     * callback for zero state. Zero state means when user entered search ui but not issued any
+     * query yet. This method can be used for client to sync up with server data if they think data
+     * might be out of sync, for example, after restart.
+     * Pre-register a callback with
+     * {@link SearchSession#registerEmptyQueryResultUpdateCallback(Executor, Callback)}
+     * is required before calling this method. Otherwise this is no-op.
+     *
+     * @see {@link SearchSession#registerEmptyQueryResultUpdateCallback(Executor, Callback)}
+     * @see {@link SearchSession.Callback#onTargetsAvailable(List)}.
+     */
+    public void requestEmptyQueryResultUpdate() {
+        if (mIsClosed.get()) {
+            throw new IllegalStateException("This client has already been destroyed.");
+        }
+
+        try {
+            mInterface.requestEmptyQueryResultUpdate(mSessionId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to request empty query result update", e);
+            e.rethrowAsRuntimeException();
+        }
+    }
+
 
     /**
      * Destroys the client and unregisters the callback. Any method on this class after this call
@@ -213,6 +307,19 @@
         }
     }
 
+    /**
+     *  Callback for receiving {@link SearchTarget} updates for zero state. Zero state
+     *  means when user entered search ui but not issued any query yet.
+     */
+    public interface Callback {
+
+        /**
+         * Called when a new set of {@link SearchTarget} are available for zero state.
+         * @param targets Sorted list of search targets.
+         */
+        void onTargetsAvailable(@NonNull List<SearchTarget> targets);
+    }
+
     static class CallbackWrapper extends Stub {
 
         private final Consumer<List<SearchTarget>> mCallback;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 30984b2..d7ab6d7 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -31,8 +31,10 @@
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.app.Activity;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
+import android.app.StatusBarManager;
 import android.bluetooth.BluetoothDevice;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
@@ -5137,6 +5139,86 @@
     public static final String EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE";
 
     /**
+     * Activity Action: Use with startActivityForResult to start a system activity that captures
+     * content on the screen to take a screenshot and present it to the user for editing. The
+     * edited screenshot is saved on device and returned to the calling activity as a {@link Uri}
+     * through {@link #getData()}. User interaction is required to return the edited screenshot to
+     * the calling activity.
+     *
+     * <p>This intent action requires the permission
+     * {@link android.Manifest.permission#LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE}.
+     *
+     * <p>Callers should query
+     * {@link StatusBarManager#canLaunchCaptureContentActivityForNote(Activity)} before showing a UI
+     * element that allows users to trigger this flow.
+     */
+    @RequiresPermission(Manifest.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE)
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE =
+            "android.intent.action.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE";
+
+    /**
+     * An int extra used by activity started with
+     * {@link #ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE} to indicate status of the response.
+     * This extra is used along with result code set to {@link android.app.Activity#RESULT_OK}.
+     *
+     * <p>The value for this extra can be one of the following:
+     * <ul>
+     *     <li>{@link #CAPTURE_CONTENT_FOR_NOTE_SUCCESS}</li>
+     *     <li>{@link #CAPTURE_CONTENT_FOR_NOTE_FAILED}</li>
+     *     <li>{@link #CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED}</li>
+     *     <li>{@link #CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED}</li>
+     *     <li>{@link #CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN}</li>
+     * </ul>
+     */
+    public static final String EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE =
+            "android.intent.extra.CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE";
+
+    /**
+     * A response code used with {@link #EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE} to indicate
+     * that the request was a success.
+     *
+     * <p>This code will only be returned after the user has interacted with the system screenshot
+     * activity to consent to sharing the data with the note.
+     *
+     * <p>The captured screenshot is returned as a {@link Uri} through {@link #getData()}.
+     */
+    public static final int CAPTURE_CONTENT_FOR_NOTE_SUCCESS = 0;
+
+    /**
+     * A response code used with {@link #EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE} to indicate
+     * that something went wrong.
+     */
+    public static final int CAPTURE_CONTENT_FOR_NOTE_FAILED = 1;
+
+    /**
+     * A response code used with {@link #EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE} to indicate
+     * that user canceled the content capture flow.
+     */
+    public static final int CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED = 2;
+
+    /**
+     * A response code used with {@link #EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE} to indicate
+     * that the intent action {@link #ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE} was started
+     * by an activity that is running in a non-supported window mode.
+     */
+    public static final int CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED = 3;
+
+    /**
+     * A response code used with {@link #EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE} to indicate
+     * that screenshot is blocked by IT admin.
+     */
+    public static final int CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN = 4;
+
+    /** @hide */
+    @IntDef(value = {
+            CAPTURE_CONTENT_FOR_NOTE_SUCCESS, CAPTURE_CONTENT_FOR_NOTE_FAILED,
+            CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED,
+            CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CaptureContentForNoteStatusCodes {}
+
+    /**
      * Broadcast Action: Sent to the integrity component when a package
      * needs to be verified. The data contains the package URI along with other relevant
      * information.
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index 8853b70..ccb53cf 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -18,9 +18,10 @@
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityManager.PendingIntentInfo;
+import android.app.ActivityOptions;
 import android.app.ActivityThread;
 import android.app.IApplicationThread;
-import android.app.ActivityManager.PendingIntentInfo;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Bundle;
 import android.os.Handler;
@@ -160,7 +161,7 @@
      */
     public void sendIntent(Context context, int code, Intent intent,
             OnFinished onFinished, Handler handler) throws SendIntentException {
-        sendIntent(context, code, intent, onFinished, handler, null);
+        sendIntent(context, code, intent, onFinished, handler, null, null /* options */);
     }
 
     /**
@@ -192,6 +193,42 @@
     public void sendIntent(Context context, int code, Intent intent,
             OnFinished onFinished, Handler handler, String requiredPermission)
             throws SendIntentException {
+        sendIntent(context, code, intent, onFinished, handler, requiredPermission,
+                null /* options */);
+    }
+
+    /**
+     * Perform the operation associated with this IntentSender, allowing the
+     * caller to specify information about the Intent to use and be notified
+     * when the send has completed.
+     *
+     * @param context The Context of the caller.  This may be null if
+     * <var>intent</var> is also null.
+     * @param code Result code to supply back to the IntentSender's target.
+     * @param intent Additional Intent data.  See {@link Intent#fillIn
+     * Intent.fillIn()} for information on how this is applied to the
+     * original Intent.  Use null to not modify the original Intent.
+     * @param onFinished The object to call back on when the send has
+     * completed, or null for no callback.
+     * @param handler Handler identifying the thread on which the callback
+     * should happen.  If null, the callback will happen from the thread
+     * pool of the process.
+     * @param requiredPermission Name of permission that a recipient of the PendingIntent
+     * is required to hold.  This is only valid for broadcast intents, and
+     * corresponds to the permission argument in
+     * {@link Context#sendBroadcast(Intent, String) Context.sendOrderedBroadcast(Intent, String)}.
+     * If null, no permission is required.
+     * @param options Additional options the caller would like to provide to modify the sending
+     * behavior.  May be built from an {@link ActivityOptions} to apply to an activity start.
+     *
+     * @throws SendIntentException Throws CanceledIntentException if the IntentSender
+     * is no longer allowing more intents to be sent through it.
+     * @hide
+     */
+    public void sendIntent(Context context, int code, Intent intent,
+            OnFinished onFinished, Handler handler, String requiredPermission,
+            @Nullable Bundle options)
+            throws SendIntentException {
         try {
             String resolvedType = intent != null ?
                     intent.resolveTypeIfNeeded(context.getContentResolver())
@@ -203,7 +240,7 @@
                     onFinished != null
                             ? new FinishedDispatcher(this, onFinished, handler)
                             : null,
-                    requiredPermission, null);
+                    requiredPermission, options);
             if (res < 0) {
                 throw new SendIntentException();
             }
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index f3209f9..49d21da 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -401,8 +401,9 @@
      *
      *         <p>If the service isn't stopped within the timeout,
      *         {@link android.app.Service#onTimeout(int)} will be called.
-     *         If the service is still not stopped after the callback,
-     *         the app will be declared an ANR.
+     *
+     *         <p>If the service is still not stopped after the callback,
+     *         the app will be declared an ANR after a short grace period of several seconds.
      *
      *     <li>
      *         A foreground service of this type cannot be made "sticky"
@@ -419,6 +420,17 @@
      *         </a>
      * </ul>
      *
+     * <p>Note, even though
+     * {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}
+     * was added
+     * on Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * it can be also used on
+     * on prior android versions (just like other new foreground service types can be used).
+     * However, because {@link android.app.Service#onTimeout(int)} did not exist on prior versions,
+     * it will never called on such versions.
+     * Because of this, developers must make sure to stop the foreground service even if
+     * {@link android.app.Service#onTimeout(int)} is not called on such versions.
+     *
      * @see android.app.Service#onTimeout(int)
      */
     public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 1 << 11;
diff --git a/core/java/android/credentials/GetCredentialOption.java b/core/java/android/credentials/CredentialOption.java
similarity index 79%
rename from core/java/android/credentials/GetCredentialOption.java
rename to core/java/android/credentials/CredentialOption.java
index f2895c7..9a3b46d 100644
--- a/core/java/android/credentials/GetCredentialOption.java
+++ b/core/java/android/credentials/CredentialOption.java
@@ -27,9 +27,11 @@
 import com.android.internal.util.Preconditions;
 
 /**
- * A specific type of credential request.
+ * Information about a specific type of credential to be requested during a {@link
+ * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor,
+ * OutcomeReceiver)} operation.
  */
-public final class GetCredentialOption implements Parcelable {
+public final class CredentialOption implements Parcelable {
 
     /**
      * Bundle key to the flattened version of the JSON request string. Framework will use this key
@@ -118,7 +120,7 @@
 
     @Override
     public String toString() {
-        return "GetCredentialOption {"
+        return "CredentialOption {"
                 + "type=" + mType
                 + ", requestData=" + mCredentialRetrievalData
                 + ", candidateQueryData=" + mCandidateQueryData
@@ -127,17 +129,17 @@
     }
 
     /**
-     * Constructs a {@link GetCredentialOption}.
+     * Constructs a {@link CredentialOption}.
      *
-     * @param type                    the requested credential type
-     * @param credentialRetrievalData the request data
-     * @param candidateQueryData      the partial request data that will be sent to the provider
-     *                                during the initial credential candidate query stage
-     * @param isSystemProviderRequired   whether the request must only be fulfilled by a system
-     *                                provider
+     * @param type                     the requested credential type
+     * @param credentialRetrievalData  the request data
+     * @param candidateQueryData       the partial request data that will be sent to the provider
+     *                                 during the initial credential candidate query stage
+     * @param isSystemProviderRequired whether the request must only be fulfilled by a system
+     *                                 provider
      * @throws IllegalArgumentException If type is empty.
      */
-    public GetCredentialOption(
+    public CredentialOption(
             @NonNull String type,
             @NonNull Bundle credentialRetrievalData,
             @NonNull Bundle candidateQueryData,
@@ -150,7 +152,7 @@
         mIsSystemProviderRequired = isSystemProviderRequired;
     }
 
-    private GetCredentialOption(@NonNull Parcel in) {
+    private CredentialOption(@NonNull Parcel in) {
         String type = in.readString8();
         Bundle data = in.readBundle();
         Bundle candidateQueryData = in.readBundle();
@@ -165,16 +167,16 @@
         mIsSystemProviderRequired = isSystemProviderRequired;
     }
 
-    public static final @NonNull Parcelable.Creator<GetCredentialOption> CREATOR =
-            new Parcelable.Creator<GetCredentialOption>() {
-                @Override
-                public GetCredentialOption[] newArray(int size) {
-                    return new GetCredentialOption[size];
-                }
+    @NonNull
+    public static final Parcelable.Creator<CredentialOption> CREATOR = new Parcelable.Creator<>() {
+        @Override
+        public CredentialOption[] newArray(int size) {
+            return new CredentialOption[size];
+        }
 
-                @Override
-                public GetCredentialOption createFromParcel(@NonNull Parcel in) {
-                    return new GetCredentialOption(in);
-                }
-            };
+        @Override
+        public CredentialOption createFromParcel(@NonNull Parcel in) {
+            return new CredentialOption(in);
+        }
+    };
 }
diff --git a/core/java/android/credentials/GetCredentialRequest.java b/core/java/android/credentials/GetCredentialRequest.java
index 85b4468..a869c5b 100644
--- a/core/java/android/credentials/GetCredentialRequest.java
+++ b/core/java/android/credentials/GetCredentialRequest.java
@@ -39,7 +39,7 @@
      * The list of credential requests.
      */
     @NonNull
-    private final List<GetCredentialOption> mGetCredentialOptions;
+    private final List<CredentialOption> mCredentialOptions;
 
     /**
      * The top request level data.
@@ -51,8 +51,8 @@
      * Returns the list of credential options to be requested.
      */
     @NonNull
-    public List<GetCredentialOption> getGetCredentialOptions() {
-        return mGetCredentialOptions;
+    public List<CredentialOption> getCredentialOptions() {
+        return mCredentialOptions;
     }
 
     /**
@@ -65,7 +65,7 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeTypedList(mGetCredentialOptions, flags);
+        dest.writeTypedList(mCredentialOptions, flags);
         dest.writeBundle(mData);
     }
 
@@ -76,29 +76,29 @@
 
     @Override
     public String toString() {
-        return "GetCredentialRequest {getCredentialOption=" + mGetCredentialOptions
+        return "GetCredentialRequest {credentialOption=" + mCredentialOptions
                 + ", data=" + mData
                 + "}";
     }
 
-    private GetCredentialRequest(@NonNull List<GetCredentialOption> getCredentialOptions,
+    private GetCredentialRequest(@NonNull List<CredentialOption> credentialOptions,
             @NonNull Bundle data) {
         Preconditions.checkCollectionNotEmpty(
-                getCredentialOptions,
-                /*valueName=*/ "getCredentialOptions");
+                credentialOptions,
+                /*valueName=*/ "credentialOptions");
         Preconditions.checkCollectionElementsNotNull(
-                getCredentialOptions,
-                /*valueName=*/ "getCredentialOptions");
-        mGetCredentialOptions = getCredentialOptions;
+                credentialOptions,
+                /*valueName=*/ "credentialOptions");
+        mCredentialOptions = credentialOptions;
         mData = requireNonNull(data,
                 "data must not be null");
     }
 
     private GetCredentialRequest(@NonNull Parcel in) {
-        List<GetCredentialOption> getCredentialOptions = new ArrayList<GetCredentialOption>();
-        in.readTypedList(getCredentialOptions, GetCredentialOption.CREATOR);
-        mGetCredentialOptions = getCredentialOptions;
-        AnnotationValidations.validate(NonNull.class, null, mGetCredentialOptions);
+        List<CredentialOption> credentialOptions = new ArrayList<CredentialOption>();
+        in.readTypedList(credentialOptions, CredentialOption.CREATOR);
+        mCredentialOptions = credentialOptions;
+        AnnotationValidations.validate(NonNull.class, null, mCredentialOptions);
 
 
         Bundle data = in.readBundle();
@@ -106,8 +106,8 @@
         AnnotationValidations.validate(NonNull.class, null, mData);
     }
 
-    public static final @NonNull Parcelable.Creator<GetCredentialRequest> CREATOR =
-            new Parcelable.Creator<GetCredentialRequest>() {
+    @NonNull public static final Parcelable.Creator<GetCredentialRequest> CREATOR =
+            new Parcelable.Creator<>() {
                 @Override
                 public GetCredentialRequest[] newArray(int size) {
                     return new GetCredentialRequest[size];
@@ -123,7 +123,7 @@
     public static final class Builder {
 
         @NonNull
-        private List<GetCredentialOption> mGetCredentialOptions = new ArrayList<>();
+        private List<CredentialOption> mCredentialOptions = new ArrayList<>();
 
         @NonNull
         private final Bundle mData;
@@ -136,43 +136,42 @@
         }
 
         /**
-         * Adds a specific type of {@link GetCredentialOption}.
+         * Adds a specific type of {@link CredentialOption}.
          */
         @NonNull
-        public Builder addGetCredentialOption(
-                @NonNull GetCredentialOption getCredentialOption) {
-            mGetCredentialOptions.add(requireNonNull(
-                    getCredentialOption, "getCredentialOption must not be null"));
+        public Builder addCredentialOption(@NonNull CredentialOption credentialOption) {
+            mCredentialOptions.add(requireNonNull(
+                    credentialOption, "credentialOption must not be null"));
             return this;
         }
 
         /**
-         * Sets the list of {@link GetCredentialOption}.
+         * Sets the list of {@link CredentialOption}.
          */
         @NonNull
-        public Builder setGetCredentialOptions(
-                @NonNull List<GetCredentialOption> getCredentialOptions) {
+        public Builder setCredentialOptions(
+                @NonNull List<CredentialOption> credentialOptions) {
             Preconditions.checkCollectionElementsNotNull(
-                    getCredentialOptions,
-                    /*valueName=*/ "getCredentialOptions");
-            mGetCredentialOptions = new ArrayList<>(getCredentialOptions);
+                    credentialOptions,
+                    /*valueName=*/ "credentialOptions");
+            mCredentialOptions = new ArrayList<>(credentialOptions);
             return this;
         }
 
         /**
          * Builds a {@link GetCredentialRequest}.
          *
-         * @throws IllegalArgumentException If getCredentialOptions is empty.
+         * @throws IllegalArgumentException If credentialOptions is empty.
          */
         @NonNull
         public GetCredentialRequest build() {
             Preconditions.checkCollectionNotEmpty(
-                    mGetCredentialOptions,
-                    /*valueName=*/ "getCredentialOptions");
+                    mCredentialOptions,
+                    /*valueName=*/ "credentialOptions");
             Preconditions.checkCollectionElementsNotNull(
-                    mGetCredentialOptions,
-                    /*valueName=*/ "getCredentialOptions");
-            return new GetCredentialRequest(mGetCredentialOptions, mData);
+                    mCredentialOptions,
+                    /*valueName=*/ "credentialOptions");
+            return new GetCredentialRequest(mCredentialOptions, mData);
         }
     }
 }
diff --git a/core/java/android/hardware/display/HdrConversionMode.java b/core/java/android/hardware/display/HdrConversionMode.java
index 1accd17..da2b016 100644
--- a/core/java/android/hardware/display/HdrConversionMode.java
+++ b/core/java/android/hardware/display/HdrConversionMode.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -119,4 +120,40 @@
         dest.writeInt(mConversionMode);
         dest.writeInt(mPreferredHdrOutputType);
     }
-}
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        return o instanceof HdrConversionMode && equals((HdrConversionMode) o);
+    }
+
+    @Override
+    public int hashCode() {
+        return 0; // don't care
+    }
+
+    @Override
+    public String toString() {
+        return "HdrConversionMode{ConversionMode=" + hdrConversionModeString(getConversionMode())
+                + ", PreferredHdrOutputType="
+                + Display.HdrCapabilities.hdrTypeToString(getPreferredHdrOutputType()) + "}";
+    }
+
+    private boolean equals(HdrConversionMode other) {
+        return other != null
+                && mConversionMode == other.getConversionMode()
+                && mPreferredHdrOutputType == other.getPreferredHdrOutputType();
+    }
+
+    private static String hdrConversionModeString(int hdrConversionMode) {
+        switch (hdrConversionMode) {
+            case HDR_CONVERSION_PASSTHROUGH:
+                return "HDR_CONVERSION_PASSTHROUGH";
+            case HDR_CONVERSION_SYSTEM:
+                return "HDR_CONVERSION_SYSTEM";
+            case HDR_CONVERSION_FORCE:
+                return "HDR_CONVERSION_FORCE";
+            default:
+                return "HDR_CONVERSION_UNKNOWN";
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java
index 0a7eb43..2d2b0fc 100644
--- a/core/java/android/os/BugreportManager.java
+++ b/core/java/android/os/BugreportManager.java
@@ -279,7 +279,7 @@
      * <p>The bugreport artifacts will be copied over to the given file descriptor only if the user
      * consents to sharing with the calling app.
      *
-     * <p>{@link BugreportManager} takes ownership of {@code bugreportFd} and {@code screenshotFd}.
+     * <p>{@link BugreportManager} takes ownership of {@code bugreportFd}.
      *
      * <p>The caller may only request to retrieve a given bugreport once. Subsequent calls will fail
      * with error code {@link BugreportCallback#BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE}.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4f96805..653998f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -15901,6 +15901,36 @@
                 "user_preferred_resolution_width";
 
         /**
+         * The HDR output mode chosen by the user. This is one of:
+         * {@link android.hardware.display.HdrConversionMode#HDR_CONVERSION_PASSTHROUGH},
+         * {@link android.hardware.display.HdrConversionMode#HDR_CONVERSION_SYSTEM},
+         * {@link android.hardware.display.HdrConversionMode#HDR_CONVERSION_FORCE}.
+         *
+         * @hide
+         */
+        @TestApi
+        @Readable
+        public static final String HDR_CONVERSION_MODE = "hdr_conversion_mode";
+
+        /**
+         * The output HDR type chosen by the user in case when {@link #HDR_CONVERSION_MODE} is
+         * {@link #HDR_CONVERSION_FORCE}. This is one of:
+         * {@link android.view.Display.HdrCapabilities#HDR_TYPE_INVALID},
+         * {@link android.view.Display.HdrCapabilities#HDR_TYPE_DOLBY_VISION},
+         * {@link android.view.Display.HdrCapabilities#HDR_TYPE_HDR10},
+         * {@link android.view.Display.HdrCapabilities#HDR_TYPE_HLG},
+         * {@link android.view.Display.HdrCapabilities#HDR_TYPE_HDR10_PLUS}
+         * <p>
+         * The value is {@link android.view.Display.HdrCapabilities#HDR_TYPE_INVALID} when user
+         * chooses SDR output type. </p>
+         *
+         * @hide
+         */
+        @TestApi
+        @Readable
+        public static final String HDR_FORCE_CONVERSION_TYPE = "hdr_force_conversion_type";
+
+        /**
          * The name of the device
          */
         @Readable
diff --git a/core/java/android/service/credentials/BeginGetCredentialRequest.java b/core/java/android/service/credentials/BeginGetCredentialRequest.java
index e375cdd..5d040db 100644
--- a/core/java/android/service/credentials/BeginGetCredentialRequest.java
+++ b/core/java/android/service/credentials/BeginGetCredentialRequest.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.app.PendingIntent;
 import android.content.Intent;
-import android.credentials.GetCredentialOption;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -33,7 +32,7 @@
 /**
  * Query stage request for getting user's credentials from a given credential provider.
  *
- * <p>This request contains a list of {@link GetCredentialOption} that have parameters
+ * <p>This request contains a list of {@link BeginGetCredentialOption} that have parameters
  * to be used to query credentials, and return a list of {@link CredentialEntry} to be set
  * on the {@link BeginGetCredentialResponse}. This list is then shown to the user on a selector.
  *
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index ee386c3..f92fd3a 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -308,7 +308,7 @@
      *
      * <p>This API denotes a query stage request for getting user's credentials from a given
      * credential provider. The request contains a list of
-     * {@link android.credentials.GetCredentialOption} that have parameters to be used for
+     * {@link BeginGetCredentialOption} that have parameters to be used for
      * populating candidate credentials, as a list of {@link CredentialEntry} to be set
      * on the {@link BeginGetCredentialResponse}. This list is then shown to the user on a
      * selector.
diff --git a/core/java/android/service/credentials/GetCredentialRequest.java b/core/java/android/service/credentials/GetCredentialRequest.java
index 4b946f0..e808ace 100644
--- a/core/java/android/service/credentials/GetCredentialRequest.java
+++ b/core/java/android/service/credentials/GetCredentialRequest.java
@@ -17,7 +17,7 @@
 package android.service.credentials;
 
 import android.annotation.NonNull;
-import android.credentials.GetCredentialOption;
+import android.credentials.CredentialOption;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -32,28 +32,30 @@
  */
 public final class GetCredentialRequest implements Parcelable {
     /** Calling package of the app requesting for credentials. */
-    private final @NonNull CallingAppInfo mCallingAppInfo;
+    @NonNull
+    private final CallingAppInfo mCallingAppInfo;
 
     /**
      * Holds parameters to be used for retrieving a specific type of credential.
      */
-    private final @NonNull GetCredentialOption mGetCredentialOption;
+    @NonNull
+    private final CredentialOption mCredentialOption;
 
     public GetCredentialRequest(@NonNull CallingAppInfo callingAppInfo,
-            @NonNull GetCredentialOption getCredentialOption) {
+            @NonNull CredentialOption credentialOption) {
         this.mCallingAppInfo = callingAppInfo;
-        this.mGetCredentialOption = getCredentialOption;
+        this.mCredentialOption = credentialOption;
     }
 
     private GetCredentialRequest(@NonNull Parcel in) {
         mCallingAppInfo = in.readTypedObject(CallingAppInfo.CREATOR);
         AnnotationValidations.validate(NonNull.class, null, mCallingAppInfo);
-        mGetCredentialOption = in.readTypedObject(GetCredentialOption.CREATOR);
-        AnnotationValidations.validate(NonNull.class, null, mGetCredentialOption);
+        mCredentialOption = in.readTypedObject(CredentialOption.CREATOR);
+        AnnotationValidations.validate(NonNull.class, null, mCredentialOption);
     }
 
-    public static final @NonNull Creator<GetCredentialRequest> CREATOR =
-            new Creator<GetCredentialRequest>() {
+    @NonNull public static final  Creator<GetCredentialRequest> CREATOR =
+            new Creator<>() {
                 @Override
                 public GetCredentialRequest createFromParcel(Parcel in) {
                     return new GetCredentialRequest(in);
@@ -73,20 +75,22 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeTypedObject(mCallingAppInfo, flags);
-        dest.writeTypedObject(mGetCredentialOption, flags);
+        dest.writeTypedObject(mCredentialOption, flags);
     }
 
     /**
      * Returns info pertaining to the app requesting credentials.
      */
-    public @NonNull CallingAppInfo getCallingAppInfo() {
+    @NonNull
+    public CallingAppInfo getCallingAppInfo() {
         return mCallingAppInfo;
     }
 
     /**
      * Returns the parameters needed to return a given type of credential.
      */
-    public @NonNull GetCredentialOption getGetCredentialOption() {
-        return mGetCredentialOption;
+    @NonNull
+    public CredentialOption getGetCredentialOption() {
+        return mCredentialOption;
     }
 }
diff --git a/core/java/android/service/search/ISearchUiService.aidl b/core/java/android/service/search/ISearchUiService.aidl
index aae66ca..bc6d421 100644
--- a/core/java/android/service/search/ISearchUiService.aidl
+++ b/core/java/android/service/search/ISearchUiService.aidl
@@ -37,5 +37,11 @@
 
     void onNotifyEvent(in SearchSessionId sessionId, in Query input, in SearchTargetEvent event);
 
+    void onRegisterEmptyQueryResultUpdateCallback (in SearchSessionId sessionId, in ISearchCallback callback);
+
+    void onRequestEmptyQueryResultUpdate(in SearchSessionId sessionId);
+
+    void onUnregisterEmptyQueryResultUpdateCallback(in SearchSessionId sessionId, in ISearchCallback callback);
+
     void onDestroy(in SearchSessionId sessionId);
 }
diff --git a/core/java/android/service/search/SearchUiService.java b/core/java/android/service/search/SearchUiService.java
index 02d41ef..55a96fa 100644
--- a/core/java/android/service/search/SearchUiService.java
+++ b/core/java/android/service/search/SearchUiService.java
@@ -20,6 +20,7 @@
 import android.annotation.CallSuper;
 import android.annotation.MainThread;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.app.search.ISearchCallback;
@@ -35,8 +36,10 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.service.search.ISearchUiService.Stub;
+import android.util.ArrayMap;
 import android.util.Slog;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -66,6 +69,9 @@
     public static final String SERVICE_INTERFACE =
             "android.service.search.SearchUiService";
 
+    private final ArrayMap<SearchSessionId, ArrayList<CallbackWrapper>>
+            mSessionEmptyQueryResultCallbacks = new ArrayMap<>();
+
     private Handler mHandler;
 
     private final android.service.search.ISearchUiService mInterface = new Stub() {
@@ -87,7 +93,7 @@
             mHandler.sendMessage(
                     obtainMessage(SearchUiService::onQuery,
                             SearchUiService.this, sessionId, input,
-                            new CallbackWrapper(callback)));
+                            new CallbackWrapper(callback, null)));
         }
 
         @Override
@@ -98,6 +104,28 @@
         }
 
         @Override
+        public void onRegisterEmptyQueryResultUpdateCallback(SearchSessionId sessionId,
+                ISearchCallback callback) {
+            mHandler.sendMessage(
+                    obtainMessage(SearchUiService::doRegisterEmptyQueryResultUpdateCallback,
+                            SearchUiService.this, sessionId, callback));
+        }
+
+        @Override
+        public void onRequestEmptyQueryResultUpdate(SearchSessionId sessionId) {
+            mHandler.sendMessage(obtainMessage(SearchUiService::doRequestEmptyQueryResultUpdate,
+                    SearchUiService.this, sessionId));
+        }
+
+        @Override
+        public void onUnregisterEmptyQueryResultUpdateCallback(SearchSessionId sessionId,
+                ISearchCallback callback) {
+            mHandler.sendMessage(
+                    obtainMessage(SearchUiService::doUnregisterEmptyQueryResultUpdateCallback,
+                            SearchUiService.this, sessionId, callback));
+        }
+
+        @Override
         public void onDestroy(SearchSessionId sessionId) {
             mHandler.sendMessage(
                     obtainMessage(SearchUiService::doDestroy,
@@ -126,21 +154,23 @@
     /**
      * Creates a new search session.
      *
+     * @removed
      * @deprecated this is method will be removed as soon as
      * {@link #onSearchSessionCreated(SearchContext, SearchSessionId)}
      * is adopted by the service.
-     *
-     * @removed
      */
     @Deprecated
     public void onCreateSearchSession(@NonNull SearchContext context,
-            @NonNull SearchSessionId sessionId) {}
+            @NonNull SearchSessionId sessionId) {
+    }
 
     /**
      * A new search session is created.
      */
     public void onSearchSessionCreated(@NonNull SearchContext context,
-            @NonNull SearchSessionId sessionId) {}
+            @NonNull SearchSessionId sessionId) {
+        mSessionEmptyQueryResultCallbacks.put(sessionId, new ArrayList<>());
+    }
 
     /**
      * Called by the client to request search results using a query string.
@@ -161,6 +191,98 @@
             @NonNull Query query,
             @NonNull SearchTargetEvent event);
 
+    private void doRegisterEmptyQueryResultUpdateCallback(@NonNull SearchSessionId sessionId,
+            @NonNull ISearchCallback callback) {
+        final ArrayList<CallbackWrapper> callbacks = mSessionEmptyQueryResultCallbacks.get(
+                sessionId);
+        if (callbacks == null) {
+            Slog.e(TAG, "Failed to register for updates for unknown session: " + sessionId);
+            return;
+        }
+
+        final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+        if (wrapper == null) {
+            callbacks.add(new CallbackWrapper(callback,
+                    callbackWrapper ->
+                            mHandler.post(() ->
+                                    removeCallbackWrapper(callbacks, callbackWrapper))));
+            if (callbacks.size() == 1) {
+                onStartUpdateEmptyQueryResult();
+            }
+        }
+    }
+
+    /**
+     * Called when the first empty query result callback is registered. Service provider may make
+     * their own decision whether to generate data if no callback is registered to optimize for
+     * system health.
+     */
+    @MainThread
+    public void onStartUpdateEmptyQueryResult() {}
+
+    private void doRequestEmptyQueryResultUpdate(@NonNull SearchSessionId sessionId) {
+        // Just an optimization, if there are no callbacks, then don't bother notifying the service
+        final ArrayList<CallbackWrapper> callbacks = mSessionEmptyQueryResultCallbacks.get(
+                sessionId);
+        if (callbacks != null && !callbacks.isEmpty()) {
+            onRequestEmptyQueryResultUpdate(sessionId);
+        }
+    }
+
+    /**
+     * Called by a client to request empty query search target result for zero state. This method
+     * is only called if there are one or more empty query result update callbacks registered.
+     *
+     * @see #updateEmptyQueryResult(SearchSessionId, List)
+     */
+    @MainThread
+    public void onRequestEmptyQueryResultUpdate(@NonNull SearchSessionId sessionId) {}
+
+    private void doUnregisterEmptyQueryResultUpdateCallback(@NonNull SearchSessionId sessionId,
+            @NonNull ISearchCallback callback) {
+        final ArrayList<CallbackWrapper> callbacks = mSessionEmptyQueryResultCallbacks.get(
+                sessionId);
+        if (callbacks == null) {
+            Slog.e(TAG, "Failed to unregister for updates for unknown session: " + sessionId);
+            return;
+        }
+
+        final CallbackWrapper wrapper = findCallbackWrapper(callbacks, callback);
+        removeCallbackWrapper(callbacks, wrapper);
+    }
+
+    /**
+     * Finds the callback wrapper for the given callback.
+     */
+    private CallbackWrapper findCallbackWrapper(ArrayList<CallbackWrapper> callbacks,
+            ISearchCallback callback) {
+        for (int i = callbacks.size() - 1; i >= 0; i--) {
+            if (callbacks.get(i).isCallback(callback)) {
+                return callbacks.get(i);
+            }
+        }
+        return null;
+    }
+
+    private void removeCallbackWrapper(@Nullable ArrayList<CallbackWrapper> callbacks,
+            @Nullable CallbackWrapper wrapper) {
+        if (callbacks == null || wrapper == null) {
+            return;
+        }
+        callbacks.remove(wrapper);
+        wrapper.destroy();
+        if (callbacks.isEmpty()) {
+            onStopUpdateEmptyQueryResult();
+        }
+    }
+
+    /**
+     * Called when there are no longer any empty query result callbacks registered. Service
+     * provider can choose to stop generating data to optimize for system health.
+     */
+    @MainThread
+    public void onStopUpdateEmptyQueryResult() {}
+
     private void doDestroy(@NonNull SearchSessionId sessionId) {
         super.onDestroy();
         onDestroy(sessionId);
@@ -172,14 +294,49 @@
     @MainThread
     public abstract void onDestroy(@NonNull SearchSessionId sessionId);
 
-    private static final class CallbackWrapper implements Consumer<List<SearchTarget>> {
+    /**
+     * Used by the service provider to send back results the client app. The can be called
+     * in response to {@link #onRequestEmptyQueryResultUpdate(SearchSessionId)} or proactively as
+     * a result of changes in zero state data.
+     */
+    public final void updateEmptyQueryResult(@NonNull SearchSessionId sessionId,
+            @NonNull List<SearchTarget> targets) {
+        List<CallbackWrapper> callbacks = mSessionEmptyQueryResultCallbacks.get(sessionId);
+        if (callbacks != null) {
+            for (CallbackWrapper callback : callbacks) {
+                callback.accept(targets);
+            }
+        }
+    }
+
+    private static final class CallbackWrapper implements Consumer<List<SearchTarget>>,
+            IBinder.DeathRecipient {
 
         private ISearchCallback mCallback;
+        private final Consumer<CallbackWrapper> mOnBinderDied;
 
-        CallbackWrapper(ISearchCallback callback) {
+        CallbackWrapper(ISearchCallback callback,
+                @Nullable Consumer<CallbackWrapper> 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 boolean isCallback(@NonNull ISearchCallback callback) {
+            if (mCallback == null) {
+                Slog.e(TAG, "Callback is null, likely the binder has died.");
+                return false;
+            }
+            return mCallback.asBinder().equals(callback.asBinder());
+        }
+
+
         @Override
         public void accept(List<SearchTarget> searchTargets) {
             try {
@@ -193,5 +350,20 @@
                 Slog.e(TAG, "Error sending result:" + e);
             }
         }
+
+        public void destroy() {
+            if (mCallback != null && mOnBinderDied != null) {
+                mCallback.asBinder().unlinkToDeath(this, 0);
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            destroy();
+            mCallback = null;
+            if (mOnBinderDied != null) {
+                mOnBinderDied.accept(this);
+            }
+        }
     }
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 44afb89..23513fad 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -1242,7 +1242,7 @@
                             null /* ignoringVisibilityState */, config.isScreenRound(),
                             false /* alwaysConsumeSystemBars */, mLayout.softInputMode,
                             mLayout.flags, SYSTEM_UI_FLAG_VISIBLE, mLayout.type,
-                            config.windowConfiguration.getWindowingMode(), null /* typeSideMap */);
+                            config.windowConfiguration.getWindowingMode(), null /* idSideMap */);
 
                     if (!fixedSize) {
                         final Rect padding = mIWallpaperEngine.mDisplayPadding;
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 91febcd..3dc79cf 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -270,10 +270,14 @@
     private static final int CALLBACK_LAST = CALLBACK_COMMIT;
 
     private Choreographer(Looper looper, int vsyncSource) {
+        this(looper, vsyncSource, /* layerHandle */ 0L);
+    }
+
+    private Choreographer(Looper looper, int vsyncSource, long layerHandle) {
         mLooper = looper;
         mHandler = new FrameHandler(looper);
         mDisplayEventReceiver = USE_VSYNC
-                ? new FrameDisplayEventReceiver(looper, vsyncSource)
+                ? new FrameDisplayEventReceiver(looper, vsyncSource, layerHandle)
                 : null;
         mLastFrameTimeNanos = Long.MIN_VALUE;
 
@@ -313,6 +317,26 @@
     }
 
     /**
+     * Gets the choreographer associated with the SurfaceControl.
+     *
+     * @param layerHandle to which the choreographer will be attached.
+     * @param looper      the choreographer is attached on this looper.
+     *
+     * @return The choreographer for the looper which is attached
+     * to the sourced SurfaceControl::mNativeHandle.
+     * @throws IllegalStateException if the looper sourced is null.
+     * @hide
+     */
+    @NonNull
+    static Choreographer getInstanceForSurfaceControl(long layerHandle,
+            @NonNull Looper looper) {
+        if (looper == null) {
+            throw new IllegalStateException("The current thread must have a looper!");
+        }
+        return new Choreographer(looper, VSYNC_SOURCE_APP, layerHandle);
+    }
+
+    /**
      * @return The Choreographer of the main thread, if it exists, or {@code null} otherwise.
      * @hide
      */
@@ -334,6 +358,15 @@
     }
 
     /**
+     * Dispose the DisplayEventReceiver on the Choreographer.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    void invalidate() {
+        dispose();
+    }
+
+    /**
      * The amount of time, in milliseconds, between each frame of the animation.
      * <p>
      * This is a requested time that the animation will attempt to honor, but the actual delay
@@ -1166,8 +1199,8 @@
         private int mFrame;
         private VsyncEventData mLastVsyncEventData = new VsyncEventData();
 
-        public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
-            super(looper, vsyncSource, 0);
+        FrameDisplayEventReceiver(Looper looper, int vsyncSource, long layerHandle) {
+            super(looper, vsyncSource, /* eventRegistration */ 0, layerHandle);
         }
 
         // TODO(b/116025192): physicalDisplayId is ignored because SF only emits VSYNC events for
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 39ea2fd..6b1499f 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -2508,5 +2508,24 @@
                     + ", mMaxAverageLuminance=" + mMaxAverageLuminance
                     + ", mMinLuminance=" + mMinLuminance + '}';
         }
+
+        /**
+         * @hide
+         */
+        @NonNull
+        public static String hdrTypeToString(int hdrType) {
+            switch (hdrType) {
+                case HDR_TYPE_DOLBY_VISION:
+                    return "HDR_TYPE_DOLBY_VISION";
+                case HDR_TYPE_HDR10:
+                    return "HDR_TYPE_HDR10";
+                case HDR_TYPE_HLG:
+                    return "HDR_TYPE_HLG";
+                case HDR_TYPE_HDR10_PLUS:
+                    return "HDR_TYPE_HDR10_PLUS";
+                default:
+                    return "HDR_TYPE_INVALID";
+            }
+        }
     }
 }
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index ce7606a0..26fda34 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -80,7 +80,7 @@
     private MessageQueue mMessageQueue;
 
     private static native long nativeInit(WeakReference<DisplayEventReceiver> receiver,
-            MessageQueue messageQueue, int vsyncSource, int eventRegistration);
+            MessageQueue messageQueue, int vsyncSource, int eventRegistration, long layerHandle);
     private static native void nativeDispose(long receiverPtr);
     @FastNative
     private static native void nativeScheduleVsync(long receiverPtr);
@@ -93,7 +93,11 @@
      */
     @UnsupportedAppUsage
     public DisplayEventReceiver(Looper looper) {
-        this(looper, VSYNC_SOURCE_APP, 0);
+        this(looper, VSYNC_SOURCE_APP, /* eventRegistration */ 0, /* layerHandle */ 0L);
+    }
+
+    public DisplayEventReceiver(Looper looper, int vsyncSource, int eventRegistration) {
+        this(looper, vsyncSource, eventRegistration, /* layerHandle */ 0L);
     }
 
     /**
@@ -103,15 +107,17 @@
      * @param vsyncSource The source of the vsync tick. Must be on of the VSYNC_SOURCE_* values.
      * @param eventRegistration Which events to dispatch. Must be a bitfield consist of the
      * EVENT_REGISTRATION_*_FLAG values.
+     * @param layerHandle Layer to which the current instance is attached to
      */
-    public DisplayEventReceiver(Looper looper, int vsyncSource, int eventRegistration) {
+    public DisplayEventReceiver(Looper looper, int vsyncSource, int eventRegistration,
+            long layerHandle) {
         if (looper == null) {
             throw new IllegalArgumentException("looper must not be null");
         }
 
         mMessageQueue = looper.getQueue();
         mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue,
-                vsyncSource, eventRegistration);
+                vsyncSource, eventRegistration, layerHandle);
     }
 
     @Override
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index e89be47..289a5b6 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -133,7 +133,8 @@
         // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching
         // this code here means that we now got control, so we can start the animation immediately.
         // If client window is trying to control IME and IME is already visible, it is immediate.
-        if (fromIme || (mState.getSource(getId()).isVisible() && getControl() != null)) {
+        if (fromIme
+                || (mState.isSourceOrDefaultVisible(getId(), getType()) && getControl() != null)) {
             return ShowResult.SHOW_IMMEDIATELY;
         }
 
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 309a94a..75f1666 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -33,12 +33,12 @@
 import static android.view.InsetsController.DEBUG;
 import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
 import static android.view.InsetsController.LayoutInsetsDuringAnimation;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.InsetsState.ISIDE_BOTTOM;
 import static android.view.InsetsState.ISIDE_FLOATING;
 import static android.view.InsetsState.ISIDE_LEFT;
 import static android.view.InsetsState.ISIDE_RIGHT;
 import static android.view.InsetsState.ISIDE_TOP;
-import static android.view.InsetsState.ITYPE_IME;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
@@ -132,19 +132,19 @@
         mController = controller;
         mInitialInsetsState = new InsetsState(state, true /* copySources */);
         if (frame != null) {
-            final SparseIntArray typeSideMap = new SparseIntArray();
-            mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */);
+            final SparseIntArray idSideMap = new SparseIntArray();
+            mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* idSideMap */);
             mHiddenInsets = calculateInsets(mInitialInsetsState, frame, controls, false /* shown */,
-                    null /* typeSideMap */);
+                    null /* idSideMap */);
             mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */,
-                    typeSideMap);
+                    idSideMap);
             mHasZeroInsetsIme = mShownInsets.bottom == 0 && controlsType(WindowInsets.Type.ime());
             if (mHasZeroInsetsIme) {
                 // IME has shownInsets of ZERO, and can't map to a side by default.
                 // Map zero insets IME to bottom, making it a special case of bottom insets.
-                typeSideMap.put(ITYPE_IME, ISIDE_BOTTOM);
+                idSideMap.put(ID_IME, ISIDE_BOTTOM);
             }
-            buildSideControlsMap(typeSideMap, mSideControlsMap, controls);
+            buildSideControlsMap(idSideMap, mSideControlsMap, controls);
         } else {
             // Passing a null frame indicates the caller wants to play the insets animation anyway,
             // no matter the source provides insets to the frame or not.
@@ -399,27 +399,27 @@
     }
 
     private Insets getInsetsFromState(InsetsState state, Rect frame,
-            @Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
+            @Nullable @InternalInsetsSide SparseIntArray idSideMap) {
         return state.calculateInsets(frame, null /* ignoringVisibilityState */,
                 false /* isScreenRound */, false /* alwaysConsumeSystemBars */,
                 LayoutParams.SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode*/,
                 0 /* legacyWindowFlags */, 0 /* legacySystemUiFlags */, TYPE_APPLICATION,
-                WINDOWING_MODE_UNDEFINED, typeSideMap).getInsets(mTypes);
+                WINDOWING_MODE_UNDEFINED, idSideMap).getInsets(mTypes);
     }
 
     /** Computes the insets relative to the given frame. */
     private Insets calculateInsets(InsetsState state, Rect frame,
             SparseArray<InsetsSourceControl> controls, boolean shown,
-            @Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
+            @Nullable @InternalInsetsSide SparseIntArray idSideMap) {
         for (int i = controls.size() - 1; i >= 0; i--) {
             final InsetsSourceControl control  = controls.valueAt(i);
             if (control == null) {
                 // control may be null if it got revoked.
                 continue;
             }
-            state.getSource(control.getId()).setVisible(shown);
+            state.setSourceVisible(control.getId(), shown);
         }
-        return getInsetsFromState(state, frame, typeSideMap);
+        return getInsetsFromState(state, frame, idSideMap);
     }
 
     /** Computes the insets from the insets hints of controls. */
@@ -435,7 +435,8 @@
                 // control may be null if it got revoked.
                 continue;
             }
-            if (state == null || state.getSource(control.getId()).isVisible()) {
+            if (state == null
+                    || state.isSourceOrDefaultVisible(control.getId(), control.getType())) {
                 insets = Insets.max(insets, control.getInsetsHint());
             }
         }
@@ -465,20 +466,23 @@
         // TODO: Implement behavior when inset spans over multiple types
         for (int i = controls.size() - 1; i >= 0; i--) {
             final InsetsSourceControl control = controls.valueAt(i);
-            final InsetsSource source = mInitialInsetsState.getSource(control.getId());
+            final InsetsSource source = mInitialInsetsState.peekSource(control.getId());
             final SurfaceControl leash = control.getLeash();
 
             mTmpMatrix.setTranslate(control.getSurfacePosition().x, control.getSurfacePosition().y);
-            mTmpFrame.set(source.getFrame());
+            if (source != null) {
+                mTmpFrame.set(source.getFrame());
+            }
             addTranslationToMatrix(side, offset, mTmpMatrix, mTmpFrame);
 
             final boolean visible = mHasZeroInsetsIme && side == ISIDE_BOTTOM
                     ? (mAnimationType == ANIMATION_TYPE_SHOW || !mFinished)
                     : inset != 0;
 
-            if (outState != null) {
-                outState.getSource(source.getId()).setVisible(visible);
-                outState.getSource(source.getId()).setFrame(mTmpFrame);
+            if (outState != null && source != null) {
+                outState.getOrCreateSource(source.getId(), source.getType())
+                        .setVisible(visible)
+                        .setFrame(mTmpFrame);
             }
 
             // If the system is controlling the insets source, the leash can be null.
@@ -517,12 +521,12 @@
         }
     }
 
-    private static void buildSideControlsMap(SparseIntArray typeSideMap,
+    private static void buildSideControlsMap(SparseIntArray idSideMap,
             SparseSetArray<InsetsSourceControl> sideControlsMap,
             SparseArray<InsetsSourceControl> controls) {
-        for (int i = typeSideMap.size() - 1; i >= 0; i--) {
-            final int type = typeSideMap.keyAt(i);
-            final int side = typeSideMap.valueAt(i);
+        for (int i = idSideMap.size() - 1; i >= 0; i--) {
+            final int type = idSideMap.keyAt(i);
+            final int side = idSideMap.valueAt(i);
             final InsetsSourceControl control = controls.get(type);
             if (control == null) {
                 // If the types that we are controlling are less than the types that the system has,
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8e8e28a..1c00e5f 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -19,12 +19,12 @@
 import static android.os.Trace.TRACE_TAG_VIEW;
 import static android.view.InsetsControllerProto.CONTROL;
 import static android.view.InsetsControllerProto.STATE;
-import static android.view.InsetsState.ITYPE_CAPTION_BAR;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
 import static android.view.WindowInsets.Type.FIRST;
 import static android.view.WindowInsets.Type.LAST;
 import static android.view.WindowInsets.Type.all;
+import static android.view.WindowInsets.Type.captionBar;
 import static android.view.WindowInsets.Type.ime;
 
 import android.animation.AnimationHandler;
@@ -44,13 +44,12 @@
 import android.os.Process;
 import android.os.Trace;
 import android.text.TextUtils;
-import android.util.ArraySet;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.InsetsSourceConsumer.ShowResult;
-import android.view.InsetsState.InternalInsetsType;
 import android.view.SurfaceControl.Transaction;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
@@ -249,6 +248,9 @@
     /** The amount IME will move up/down when animating in floating mode. */
     private static final int FLOATING_IME_BOTTOM_INSET_DP = -80;
 
+    private static final int ID_CAPTION_BAR =
+            InsetsSource.createId(null /* owner */, 0 /* index */, captionBar());
+
     static final boolean DEBUG = false;
     static final boolean WARN = false;
 
@@ -621,6 +623,79 @@
     private final Runnable mInvokeControllableInsetsChangedListeners =
             this::invokeControllableInsetsChangedListeners;
 
+    private final InsetsState.OnTraverseCallbacks mRemoveGoneSources =
+            new InsetsState.OnTraverseCallbacks() {
+
+                private final IntArray mPendingRemoveIndexes = new IntArray();
+
+                @Override
+                public void onIdNotFoundInState2(int index1, InsetsSource source1) {
+                    if (!CAPTION_ON_SHELL && source1.getType() == captionBar()) {
+                        return;
+                    }
+
+                    // Don't change the indexes of the sources while traversing. Remove it later.
+                    mPendingRemoveIndexes.add(index1);
+
+                    // Remove the consumer as well except the IME one. IME consumer should always
+                    // be there since we need to communicate with InputMethodManager no matter we
+                    // have the source or not.
+                    if (source1.getType() != ime()) {
+                        mSourceConsumers.remove(source1.getId());
+                    }
+                }
+
+                @Override
+                public void onFinish(InsetsState state1, InsetsState state2) {
+                    for (int i = mPendingRemoveIndexes.size() - 1; i >= 0; i--) {
+                        state1.removeSourceAt(mPendingRemoveIndexes.get(i));
+                    }
+                    mPendingRemoveIndexes.clear();
+                }
+            };
+
+    private final InsetsState.OnTraverseCallbacks mStartResizingAnimationIfNeeded =
+            new InsetsState.OnTraverseCallbacks() {
+
+                private @InsetsType int mTypes;
+                private InsetsState mToState;
+
+                @Override
+                public void onStart(InsetsState state1, InsetsState state2) {
+                    mTypes = 0;
+                    mToState = null;
+                }
+
+                @Override
+                public void onIdMatch(InsetsSource source1, InsetsSource source2) {
+                    final @InsetsType int type = source1.getType();
+                    if ((type & Type.systemBars()) == 0
+                            || !source1.isVisible() || !source2.isVisible()
+                            || source1.getFrame().equals(source2.getFrame())
+                            || !(Rect.intersects(mFrame, source1.getFrame())
+                                    || Rect.intersects(mFrame, source2.getFrame()))) {
+                        return;
+                    }
+                    mTypes |= type;
+                    if (mToState == null) {
+                        mToState = new InsetsState();
+                    }
+                    mToState.addSource(new InsetsSource(source2));
+                }
+
+                @Override
+                public void onFinish(InsetsState state1, InsetsState state2) {
+                    if (mTypes == 0) {
+                        return;
+                    }
+                    cancelExistingControllers(mTypes);
+                    final InsetsAnimationControlRunner runner = new InsetsResizeAnimationRunner(
+                            mFrame, state1, mToState, RESIZE_INTERPOLATOR,
+                            ANIMATION_DURATION_RESIZE, mTypes, InsetsController.this);
+                    mRunningAnimations.add(new RunningAnimation(runner, runner.getAnimationType()));
+                }
+            };
+
     public InsetsController(Host host) {
         this(host, (controller, source) -> {
             if (source.getType() == ime()) {
@@ -671,7 +746,7 @@
             WindowInsets insets = state.calculateInsets(mFrame, mState /* ignoringVisibilityState*/,
                     mLastInsets.isRound(), mLastInsets.shouldAlwaysConsumeSystemBars(),
                     mLastLegacySoftInputMode, mLastLegacyWindowFlags, mLastLegacySystemUiFlags,
-                    mWindowType, mLastWindowingMode, null /* typeSideMap */);
+                    mWindowType, mLastWindowingMode, null /* idSideMap */);
             mHost.dispatchWindowInsetsAnimationProgress(insets,
                     Collections.unmodifiableList(runningAnimations));
             if (DEBUG) {
@@ -687,7 +762,7 @@
         };
 
         // Make mImeSourceConsumer always non-null.
-        mImeSourceConsumer = getSourceConsumer(new InsetsSource(ITYPE_IME, ime()));
+        mImeSourceConsumer = getSourceConsumer(new InsetsSource(ID_IME, ime()));
     }
 
     @VisibleForTesting
@@ -739,29 +814,21 @@
                 true /* excludeInvisibleIme */)) {
             if (DEBUG) Log.d(TAG, "onStateChanged, notifyInsetsChanged");
             mHost.notifyInsetsChanged();
-            startResizingAnimationIfNeeded(lastState);
+            if (lastState.getDisplayFrame().equals(mState.getDisplayFrame())) {
+                InsetsState.traverse(lastState, mState, mStartResizingAnimationIfNeeded);
+            }
         }
         return true;
     }
 
     private void updateState(InsetsState newState) {
         mState.set(newState, 0 /* types */);
-        for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
-            final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
-            final InsetsSource source = newState.peekSource(consumer.getId());
-            if (source == null && consumer != mImeSourceConsumer) {
-                // IME source consumer should always be there since we need to communicate with
-                // InputMethodManager no matter we have the source or not.
-                mSourceConsumers.removeAt(i);
-            }
-        }
         @InsetsType int existingTypes = 0;
         @InsetsType int visibleTypes = 0;
         @InsetsType int disabledUserAnimationTypes = 0;
         @InsetsType int[] cancelledUserAnimationTypes = {0};
-        for (int i = 0; i < InsetsState.SIZE; i++) {
-            InsetsSource source = newState.peekSource(i);
-            if (source == null) continue;
+        for (int i = 0, size = newState.sourceSize(); i < size; i++) {
+            final InsetsSource source = newState.sourceAt(i);
             @InsetsType int type = source.getType();
             @AnimationType int animationType = getAnimationType(type);
             if (!source.isUserControllable()) {
@@ -789,15 +856,7 @@
             }
             mVisibleTypes = visibleTypes;
         }
-        for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
-            // Only update the server side insets here.
-            if (!CAPTION_ON_SHELL && type == ITYPE_CAPTION_BAR) continue;
-            InsetsSource source = mState.peekSource(type);
-            if (source == null) continue;
-            if (newState.peekSource(type) == null) {
-                mState.removeSource(type);
-            }
-        }
+        InsetsState.traverse(mState, newState, mRemoveGoneSources);
 
         updateDisabledUserAnimationTypes(disabledUserAnimationTypes);
 
@@ -825,52 +884,17 @@
         if (CAPTION_ON_SHELL) {
             return false;
         }
-        if (mState.peekSource(ITYPE_CAPTION_BAR) == null
-                && mCaptionInsetsHeight == 0) {
+        final InsetsSource source = mState.peekSource(ID_CAPTION_BAR);
+        if (source == null && mCaptionInsetsHeight == 0) {
             return false;
         }
-        if (mState.peekSource(ITYPE_CAPTION_BAR) != null
-                && mCaptionInsetsHeight
-                == mState.peekSource(ITYPE_CAPTION_BAR).getFrame().height()) {
+        if (source != null && mCaptionInsetsHeight == source.getFrame().height()) {
             return false;
         }
 
         return true;
     }
 
-    private void startResizingAnimationIfNeeded(InsetsState fromState) {
-        if (!fromState.getDisplayFrame().equals(mState.getDisplayFrame())) {
-            return;
-        }
-        @InsetsType int types = 0;
-        InsetsState toState = null;
-        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(Type.systemBars());
-        for (int i = internalTypes.size() - 1; i >= 0; i--) {
-            final @InternalInsetsType int type = internalTypes.valueAt(i);
-            final InsetsSource fromSource = fromState.peekSource(type);
-            final InsetsSource toSource = mState.peekSource(type);
-            if (fromSource != null && toSource != null
-                    && fromSource.isVisible() && toSource.isVisible()
-                    && !fromSource.getFrame().equals(toSource.getFrame())
-                    && (Rect.intersects(mFrame, fromSource.getFrame())
-                            || Rect.intersects(mFrame, toSource.getFrame()))) {
-                types |= toSource.getType();
-                if (toState == null) {
-                    toState = new InsetsState();
-                }
-                toState.addSource(new InsetsSource(toSource));
-            }
-        }
-        if (types == 0) {
-            return;
-        }
-        cancelExistingControllers(types);
-        final InsetsAnimationControlRunner runner = new InsetsResizeAnimationRunner(
-                mFrame, fromState, toState, RESIZE_INTERPOLATOR, ANIMATION_DURATION_RESIZE, types,
-                this);
-        mRunningAnimations.add(new RunningAnimation(runner, runner.getAnimationType()));
-    }
-
     /**
      * @see InsetsState#calculateInsets(Rect, InsetsState, boolean, boolean, int, int, int, int,
      *      int, SparseIntArray)
@@ -886,7 +910,7 @@
         mLastLegacySystemUiFlags = legacySystemUiFlags;
         mLastInsets = mState.calculateInsets(mFrame, null /* ignoringVisibilityState*/,
                 isScreenRound, alwaysConsumeSystemBars, legacySoftInputMode, legacyWindowFlags,
-                legacySystemUiFlags, windowType, windowingMode, null /* typeSideMap */);
+                legacySystemUiFlags, windowType, windowingMode, null /* idSideMap */);
         return mLastInsets;
     }
 
@@ -1217,7 +1241,8 @@
                 ImeTracker.get().onFailed(statsToken,
                         ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
 
-                if (fromIme && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) {
+                if (fromIme
+                        && !mState.isSourceOrDefaultVisible(mImeSourceConsumer.getId(), ime())) {
                     // We've requested IMM to show IME, but the IME is not controllable. We need to
                     // cancel the request.
                     setRequestedVisibleTypes(0 /* visibleTypes */, ime());
@@ -1757,10 +1782,10 @@
         if (mCaptionInsetsHeight != height) {
             mCaptionInsetsHeight = height;
             if (mCaptionInsetsHeight != 0) {
-                mState.getSource(ITYPE_CAPTION_BAR).setFrame(mFrame.left, mFrame.top,
-                        mFrame.right, mFrame.top + mCaptionInsetsHeight);
+                mState.getOrCreateSource(ID_CAPTION_BAR, captionBar()).setFrame(
+                        mFrame.left, mFrame.top, mFrame.right, mFrame.top + mCaptionInsetsHeight);
             } else {
-                mState.removeSource(ITYPE_CAPTION_BAR);
+                mState.removeSource(ID_CAPTION_BAR);
             }
             mHost.notifyInsetsChanged();
         }
diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java
index cf64eedf..bffaeea 100644
--- a/core/java/android/view/InsetsResizeAnimationRunner.java
+++ b/core/java/android/view/InsetsResizeAnimationRunner.java
@@ -33,7 +33,6 @@
 import android.graphics.Rect;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
-import android.view.InsetsState.InternalInsetsType;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation.Bounds;
 import android.view.animation.Interpolator;
@@ -142,24 +141,23 @@
             return false;
         }
         final float fraction = mAnimation.getInterpolatedFraction();
-        for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
-            final InsetsSource fromSource = mFromState.peekSource(type);
-            final InsetsSource toSource = mToState.peekSource(type);
-            if (fromSource == null || toSource == null) {
-                continue;
+        InsetsState.traverse(mFromState, mToState, new InsetsState.OnTraverseCallbacks() {
+            @Override
+            public void onIdMatch(InsetsSource fromSource, InsetsSource toSource) {
+                final Rect fromFrame = fromSource.getFrame();
+                final Rect toFrame = toSource.getFrame();
+                final Rect frame = new Rect(
+                        (int) (fromFrame.left + fraction * (toFrame.left - fromFrame.left)),
+                        (int) (fromFrame.top + fraction * (toFrame.top - fromFrame.top)),
+                        (int) (fromFrame.right + fraction * (toFrame.right - fromFrame.right)),
+                        (int) (fromFrame.bottom + fraction * (toFrame.bottom - fromFrame.bottom)));
+                final InsetsSource source =
+                        new InsetsSource(fromSource.getId(), fromSource.getType());
+                source.setFrame(frame);
+                source.setVisible(toSource.isVisible());
+                outState.addSource(source);
             }
-            final Rect fromFrame = fromSource.getFrame();
-            final Rect toFrame = toSource.getFrame();
-            final Rect frame = new Rect(
-                    (int) (fromFrame.left + fraction * (toFrame.left - fromFrame.left)),
-                    (int) (fromFrame.top + fraction * (toFrame.top - fromFrame.top)),
-                    (int) (fromFrame.right + fraction * (toFrame.right - fromFrame.right)),
-                    (int) (fromFrame.bottom + fraction * (toFrame.bottom - fromFrame.bottom)));
-            final InsetsSource source = new InsetsSource(type, fromSource.getType());
-            source.setFrame(frame);
-            source.setVisible(toSource.isVisible());
-            outState.addSource(source);
-        }
+        });
         if (mFinished) {
             mController.notifyFinished(this, true /* shown */);
         }
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index f6b063d..17ab83c 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -21,7 +21,9 @@
 import static android.view.InsetsSourceProto.VISIBLE;
 import static android.view.InsetsSourceProto.VISIBLE_FRAME;
 import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
+import static android.view.WindowInsets.Type.ime;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Insets;
@@ -40,6 +42,9 @@
  */
 public class InsetsSource implements Parcelable {
 
+    /** The insets source ID of IME */
+    public static final int ID_IME = createId(null, 0, ime());
+
     /**
      * An unique integer to identify this source across processes.
      */
@@ -83,20 +88,24 @@
         mInsetsRoundedCornerFrame = other.mInsetsRoundedCornerFrame;
     }
 
-    public void setFrame(int left, int top, int right, int bottom) {
+    public InsetsSource setFrame(int left, int top, int right, int bottom) {
         mFrame.set(left, top, right, bottom);
+        return this;
     }
 
-    public void setFrame(Rect frame) {
+    public InsetsSource setFrame(Rect frame) {
         mFrame.set(frame);
+        return this;
     }
 
-    public void setVisibleFrame(@Nullable Rect visibleFrame) {
+    public InsetsSource setVisibleFrame(@Nullable Rect visibleFrame) {
         mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : null;
+        return this;
     }
 
-    public void setVisible(boolean visible) {
+    public InsetsSource setVisible(boolean visible) {
         mVisible = visible;
+        return this;
     }
 
     public int getId() {
@@ -128,8 +137,9 @@
         return mInsetsRoundedCornerFrame;
     }
 
-    public void setInsetsRoundedCornerFrame(boolean insetsRoundedCornerFrame) {
+    public InsetsSource setInsetsRoundedCornerFrame(boolean insetsRoundedCornerFrame) {
         mInsetsRoundedCornerFrame = insetsRoundedCornerFrame;
+        return this;
     }
 
     /**
@@ -223,6 +233,29 @@
     }
 
     /**
+     * Creates an identifier of an {@link InsetsSource}.
+     *
+     * @param owner An object owned by the owner. Only the owner can modify its own sources.
+     * @param index An owner may have multiple sources with the same type. For example, the system
+     *              server might have multiple display cutout sources. This is used to identify
+     *              which one is which. The value must be in a range of [0, 2047].
+     * @param type The {@link WindowInsets.Type.InsetsType type} of the source.
+     * @return a unique integer as the identifier.
+     */
+    public static int createId(Object owner, @IntRange(from = 0, to = 2047) int index,
+            @InsetsType int type) {
+        if (index < 0 || index >= 2048) {
+            throw new IllegalArgumentException();
+        }
+        // owner takes top 16 bits;
+        // index takes 11 bits since the 6th bit;
+        // type takes bottom 5 bits.
+        return (((owner != null ? owner.hashCode() : 1) % (1 << 16)) << 16)
+                + (index << 5)
+                + WindowInsets.Type.indexOf(type);
+    }
+
+    /**
      * Export the state of {@link InsetsSource} into a protocol buffer output stream.
      *
      * @param proto   Stream to write the state to
@@ -241,7 +274,7 @@
 
     public void dump(String prefix, PrintWriter pw) {
         pw.print(prefix);
-        pw.print("InsetsSource id="); pw.print(mId);
+        pw.print("InsetsSource id="); pw.print(Integer.toHexString(mId));
         pw.print(" type="); pw.print(WindowInsets.Type.toString(mType));
         pw.print(" frame="); pw.print(mFrame.toShortString());
         if (mVisibleFrame != null) {
@@ -258,7 +291,7 @@
     }
 
     /**
-     * @param excludeInvisibleImeFrames If {@link InsetsState#ITYPE_IME} frames should be ignored
+     * @param excludeInvisibleImeFrames If {@link WindowInsets.Type#ime()} frames should be ignored
      *                                  when IME is not visible.
      */
     public boolean equals(@Nullable Object o, boolean excludeInvisibleImeFrames) {
@@ -316,8 +349,7 @@
 
     @Override
     public String toString() {
-        return "InsetsSource: {"
-                + "mId=" + mId
+        return "InsetsSource: {" + Integer.toHexString(mId)
                 + " mType=" + WindowInsets.Type.toString(mType)
                 + " mFrame=" + mFrame.toShortString()
                 + " mVisible=" + mVisible
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index c849cb5..7ea93f5 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -205,8 +205,7 @@
 
     @Override
     public String toString() {
-        return "InsetsSourceControl: {"
-                + "mId=" + mId
+        return "InsetsSourceControl: {" + Integer.toHexString(mId)
                 + " mType=" + WindowInsets.Type.toString(mType)
                 + (mInitiallyVisible ? " initiallyVisible" : "")
                 + " mSurfacePosition=" + mSurfacePosition
@@ -217,7 +216,7 @@
 
     public void dump(String prefix, PrintWriter pw) {
         pw.print(prefix);
-        pw.print("InsetsSourceControl mId="); pw.print(mId);
+        pw.print("InsetsSourceControl mId="); pw.print(Integer.toHexString(mId));
         pw.print(" mType="); pw.print(WindowInsets.Type.toString(mType));
         pw.print(" mLeash="); pw.print(mLeash);
         pw.print(" mInitiallyVisible="); pw.print(mInitiallyVisible);
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 054d177..70a7739 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -20,6 +20,7 @@
 import static android.view.InsetsStateProto.DISPLAY_FRAME;
 import static android.view.InsetsStateProto.SOURCES;
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
+import static android.view.WindowInsets.Type.captionBar;
 import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.indexOf;
@@ -42,6 +43,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArraySet;
+import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.WindowInsets.Type;
@@ -53,7 +55,6 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
 import java.util.Objects;
 import java.util.StringJoiner;
 
@@ -85,11 +86,6 @@
             ITYPE_TOP_TAPPABLE_ELEMENT,
             ITYPE_RIGHT_TAPPABLE_ELEMENT,
             ITYPE_BOTTOM_TAPPABLE_ELEMENT,
-            ITYPE_LEFT_DISPLAY_CUTOUT,
-            ITYPE_TOP_DISPLAY_CUTOUT,
-            ITYPE_RIGHT_DISPLAY_CUTOUT,
-            ITYPE_BOTTOM_DISPLAY_CUTOUT,
-            ITYPE_IME,
             ITYPE_CLIMATE_BAR,
             ITYPE_EXTRA_NAVIGATION_BAR,
             ITYPE_LEFT_GENERIC_OVERLAY,
@@ -99,15 +95,7 @@
     })
     public @interface InternalInsetsType {}
 
-    /**
-     * Special value to be used to by methods returning an {@link InternalInsetsType} to indicate
-     * that the objects/parameters aren't associated with an {@link InternalInsetsType}
-     */
-    public static final int ITYPE_INVALID = -1;
-
-    static final int FIRST_TYPE = 0;
-
-    public static final int ITYPE_STATUS_BAR = FIRST_TYPE;
+    public static final int ITYPE_STATUS_BAR = 0;
     public static final int ITYPE_NAVIGATION_BAR = 1;
     public static final int ITYPE_CAPTION_BAR = 2;
 
@@ -121,19 +109,11 @@
     public static final int ITYPE_LEFT_MANDATORY_GESTURES = 9;
     public static final int ITYPE_RIGHT_MANDATORY_GESTURES = 10;
 
-    public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 11;
-    public static final int ITYPE_TOP_DISPLAY_CUTOUT = 12;
-    public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 13;
-    public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 14;
-
     public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 15;
     public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 16;
     public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = 17;
     public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 18;
 
-    /** Input method window. */
-    public static final int ITYPE_IME = 19;
-
     /** Additional system decorations inset type. */
     public static final int ITYPE_CLIMATE_BAR = 20;
     public static final int ITYPE_EXTRA_NAVIGATION_BAR = 21;
@@ -144,16 +124,8 @@
     public static final int ITYPE_RIGHT_GENERIC_OVERLAY = 24;
     public static final int ITYPE_BOTTOM_GENERIC_OVERLAY = 25;
 
-    static final int LAST_TYPE = ITYPE_BOTTOM_GENERIC_OVERLAY;
-    public static final int SIZE = LAST_TYPE + 1;
-
-    // Derived types
-
-    /** A shelf is the same as the navigation bar. */
-    public static final int ITYPE_SHELF = ITYPE_NAVIGATION_BAR;
-
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = "IINSETS_SIDE", value = {
+    @IntDef(prefix = "ISIDE", value = {
             ISIDE_LEFT,
             ISIDE_TOP,
             ISIDE_RIGHT,
@@ -169,7 +141,7 @@
     static final int ISIDE_FLOATING = 4;
     static final int ISIDE_UNKNOWN = 5;
 
-    private final InsetsSource[] mSources = new InsetsSource[SIZE];
+    private final SparseArray<InsetsSource> mSources;
 
     /**
      * The frame of the display these sources are relative to.
@@ -201,13 +173,15 @@
     private DisplayShape mDisplayShape = DisplayShape.NONE;
 
     public InsetsState() {
+        mSources = new SparseArray<>();
     }
 
     public InsetsState(InsetsState copy) {
-        set(copy);
+        this(copy, false /* copySources */);
     }
 
     public InsetsState(InsetsState copy, boolean copySources) {
+        mSources = new SparseArray<>(copy.mSources.size());
         set(copy, copySources);
     }
 
@@ -224,36 +198,29 @@
             boolean isScreenRound, boolean alwaysConsumeSystemBars,
             int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags,
             int windowType, @WindowConfiguration.WindowingMode int windowingMode,
-            @Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
+            @Nullable @InternalInsetsSide SparseIntArray idSideMap) {
         Insets[] typeInsetsMap = new Insets[Type.SIZE];
         Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
         boolean[] typeVisibilityMap = new boolean[Type.SIZE];
         final Rect relativeFrame = new Rect(frame);
         final Rect relativeFrameMax = new Rect(frame);
-        for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            InsetsSource source = mSources[type];
-            if (source == null) {
-                int index = indexOf(toPublicType(type));
-                if (typeInsetsMap[index] == null) {
-                    typeInsetsMap[index] = Insets.NONE;
-                }
-                continue;
-            }
+        for (int i = mSources.size() - 1; i >= 0; i--) {
+            final InsetsSource source = mSources.valueAt(i);
 
             processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
-                    typeSideMap, typeVisibilityMap);
+                    idSideMap, typeVisibilityMap);
 
             // IME won't be reported in max insets as the size depends on the EditorInfo of the IME
             // target.
             if (source.getType() != WindowInsets.Type.ime()) {
                 InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null
-                        ? ignoringVisibilityState.getSource(type)
+                        ? ignoringVisibilityState.peekSource(source.getId())
                         : source;
                 if (ignoringVisibilitySource == null) {
                     continue;
                 }
                 processSource(ignoringVisibilitySource, relativeFrameMax,
-                        true /* ignoreVisibility */, typeMaxInsetsMap, null /* typeSideMap */,
+                        true /* ignoreVisibility */, typeMaxInsetsMap, null /* idSideMap */,
                         null /* typeVisibilityMap */);
             }
         }
@@ -306,8 +273,9 @@
         // If mRoundedCornerFrame is set, we should calculate the new RoundedCorners based on this
         // frame.
         final Rect roundedCornerFrame = new Rect(mRoundedCornerFrame);
-        for (InsetsSource source : mSources) {
-            if (source != null && source.getInsetsRoundedCornerFrame()) {
+        for (int i = mSources.size() - 1; i >= 0; i--) {
+            final InsetsSource source = mSources.valueAt(i);
+            if (source.getInsetsRoundedCornerFrame()) {
                 final Insets insets = source.calculateInsets(roundedCornerFrame, false);
                 roundedCornerFrame.inset(insets);
             }
@@ -351,13 +319,9 @@
 
     public Insets calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) {
         Insets insets = Insets.NONE;
-        for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            InsetsSource source = mSources[type];
-            if (source == null) {
-                continue;
-            }
-            int publicType = InsetsState.toPublicType(type);
-            if ((publicType & types) == 0) {
+        for (int i = mSources.size() - 1; i >= 0; i--) {
+            final InsetsSource source = mSources.valueAt(i);
+            if ((source.getType() & types) == 0) {
                 continue;
             }
             insets = Insets.max(source.calculateInsets(frame, ignoreVisibility), insets);
@@ -368,13 +332,9 @@
     public Insets calculateInsets(Rect frame, @InsetsType int types,
             @InsetsType int requestedVisibleTypes) {
         Insets insets = Insets.NONE;
-        for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            InsetsSource source = mSources[type];
-            if (source == null) {
-                continue;
-            }
-            int publicType = InsetsState.toPublicType(type);
-            if ((publicType & types & requestedVisibleTypes) == 0) {
+        for (int i = mSources.size() - 1; i >= 0; i--) {
+            final InsetsSource source = mSources.valueAt(i);
+            if ((source.getType() & types & requestedVisibleTypes) == 0) {
                 continue;
             }
             insets = Insets.max(source.calculateInsets(frame, true), insets);
@@ -392,13 +352,9 @@
                 ? systemBars() | ime()
                 : systemBars();
         Insets insets = Insets.NONE;
-        for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            InsetsSource source = mSources[type];
-            if (source == null) {
-                continue;
-            }
-            final int publicType = InsetsState.toPublicType(type);
-            if ((publicType & visibleInsetsTypes) == 0) {
+        for (int i = mSources.size() - 1; i >= 0; i--) {
+            final InsetsSource source = mSources.valueAt(i);
+            if ((source.getType() & visibleInsetsTypes) == 0) {
                 continue;
             }
             insets = Insets.max(source.calculateVisibleInsets(frame), insets);
@@ -416,13 +372,10 @@
     @InsetsType
     public int calculateUncontrollableInsetsFromFrame(Rect frame) {
         int blocked = 0;
-        for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            InsetsSource source = mSources[type];
-            if (source == null) {
-                continue;
-            }
+        for (int i = mSources.size() - 1; i >= 0; i--) {
+            final InsetsSource source = mSources.valueAt(i);
             if (!canControlSource(frame, source)) {
-                blocked |= toPublicType(type);
+                blocked |= source.getType();
             }
         }
         return blocked;
@@ -438,12 +391,12 @@
     }
 
     private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
-            Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray typeSideMap,
+            Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray idSideMap,
             @Nullable boolean[] typeVisibilityMap) {
         Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
 
         final int type = source.getType();
-        processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+        processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
                 insets, type);
 
         if (type == Type.MANDATORY_SYSTEM_GESTURES) {
@@ -452,24 +405,24 @@
             //       Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the
             //       ability to set systemGestureInsets() independently from
             //       mandatorySystemGestureInsets() in the Builder.
-            processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+            processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
                     insets, Type.SYSTEM_GESTURES);
         }
         if (type == Type.CAPTION_BAR) {
             // Caption should also be gesture and tappable elements. This should not be needed when
             // the caption is added from the shell, as the shell can add other types at the same
             // time.
-            processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+            processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
                     insets, Type.SYSTEM_GESTURES);
-            processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+            processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
                     insets, Type.MANDATORY_SYSTEM_GESTURES);
-            processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
+            processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap,
                     insets, Type.TAPPABLE_ELEMENT);
         }
     }
 
     private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap,
-            @InternalInsetsSide @Nullable SparseIntArray typeSideMap,
+            @InternalInsetsSide @Nullable SparseIntArray idSideMap,
             @Nullable boolean[] typeVisibilityMap, Insets insets, int type) {
         int index = indexOf(type);
         Insets existing = typeInsetsMap[index];
@@ -483,10 +436,10 @@
             typeVisibilityMap[index] = source.isVisible();
         }
 
-        if (typeSideMap != null) {
+        if (idSideMap != null) {
             @InternalInsetsSide int insetSide = getInsetSide(insets);
             if (insetSide != ISIDE_UNKNOWN) {
-                typeSideMap.put(source.getId(), insetSide);
+                idSideMap.put(source.getId(), insetSide);
             }
         }
     }
@@ -514,31 +467,60 @@
         return ISIDE_UNKNOWN;
     }
 
-    public InsetsSource getSource(@InternalInsetsType int type) {
-        InsetsSource source = mSources[type];
+    /**
+     * Gets the source mapped from the ID, or creates one if no such mapping has been made.
+     */
+    public InsetsSource getOrCreateSource(int id, int type) {
+        InsetsSource source = mSources.get(id);
         if (source != null) {
             return source;
         }
-        source = new InsetsSource(type, toPublicType(type));
-        mSources[type] = source;
+        source = new InsetsSource(id, type);
+        mSources.put(id, source);
         return source;
     }
 
-    public @Nullable InsetsSource peekSource(@InternalInsetsType int type) {
-        return mSources[type];
+    /**
+     * Gets the source mapped from the ID, or <code>null</code> if no such mapping has been made.
+     */
+    public @Nullable InsetsSource peekSource(int id) {
+        return mSources.get(id);
     }
 
     /**
-     * Returns the source visibility or the default visibility if the source doesn't exist. This is
-     * useful if when treating this object as a request.
+     * Given an index in the range <code>0...sourceSize()-1</code>, returns the source ID from the
+     * <code>index</code>th ID-source mapping that this state stores.
+     */
+    public int sourceIdAt(int index) {
+        return mSources.keyAt(index);
+    }
+
+    /**
+     * Given an index in the range <code>0...sourceSize()-1</code>, returns the source from the
+     * <code>index</code>th ID-source mapping that this state stores.
+     */
+    public InsetsSource sourceAt(int index) {
+        return mSources.valueAt(index);
+    }
+
+    /**
+     * Returns the amount of the sources.
+     */
+    public int sourceSize() {
+        return mSources.size();
+    }
+
+    /**
+     * Returns if the source is visible or the type is default visible and the source doesn't exist.
      *
-     * @param type The {@link InternalInsetsType} to query.
+     * @param id The ID of the source.
+     * @param type The {@link InsetsType} to see if it is default visible.
      * @return {@code true} if the source is visible or the type is default visible and the source
      *         doesn't exist.
      */
-    public boolean getSourceOrDefaultVisibility(@InternalInsetsType int type) {
-        final InsetsSource source = mSources[type];
-        return source != null ? source.isVisible() : getDefaultVisibility(type);
+    public boolean isSourceOrDefaultVisible(int id, @InsetsType int type) {
+        final InsetsSource source = mSources.get(id);
+        return source != null ? source.isVisible() : (type & Type.defaultVisible()) != 0;
     }
 
     public void setDisplayFrame(Rect frame) {
@@ -612,28 +594,31 @@
     }
 
     /**
-     * Modifies the state of this class to exclude a certain type to make it ready for dispatching
-     * to the client.
+     * Removes the source which has the ID from this state, if there was any.
      *
-     * @param type The {@link InternalInsetsType} of the source to remove
-     * @return {@code true} if this InsetsState was modified; {@code false} otherwise.
+     * @param id The ID of the source to remove.
      */
-    public boolean removeSource(@InternalInsetsType int type) {
-        if (mSources[type] == null) {
-            return false;
-        }
-        mSources[type] = null;
-        return true;
+    public void removeSource(int id) {
+        mSources.delete(id);
+    }
+
+    /**
+     * Removes the source at the specified index.
+     *
+     * @param index The index of the source to remove.
+     */
+    public void removeSourceAt(int index) {
+        mSources.removeAt(index);
     }
 
     /**
      * A shortcut for setting the visibility of the source.
      *
-     * @param type The {@link InternalInsetsType} of the source to set the visibility
+     * @param id The ID of the source to set the visibility
      * @param visible {@code true} for visible
      */
-    public void setSourceVisible(@InternalInsetsType int type, boolean visible) {
-        InsetsSource source = mSources[type];
+    public void setSourceVisible(int id, boolean visible) {
+        final InsetsSource source = mSources.get(id);
         if (source != null) {
             source.setVisible(visible);
         }
@@ -651,14 +636,12 @@
         mRoundedCornerFrame.scale(scale);
         mPrivacyIndicatorBounds = mPrivacyIndicatorBounds.scale(scale);
         mDisplayShape = mDisplayShape.setScale(scale);
-        for (int i = 0; i < SIZE; i++) {
-            final InsetsSource source = mSources[i];
-            if (source != null) {
-                source.getFrame().scale(scale);
-                final Rect visibleFrame = source.getVisibleFrame();
-                if (visibleFrame != null) {
-                    visibleFrame.scale(scale);
-                }
+        for (int i = mSources.size() - 1; i >= 0; i--) {
+            final InsetsSource source = mSources.valueAt(i);
+            source.getFrame().scale(scale);
+            final Rect visibleFrame = source.getVisibleFrame();
+            if (visibleFrame != null) {
+                visibleFrame.scale(scale);
             }
         }
     }
@@ -674,15 +657,12 @@
         mRoundedCornerFrame.set(other.mRoundedCornerFrame);
         mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
         mDisplayShape = other.getDisplayShape();
-        if (copySources) {
-            for (int i = 0; i < SIZE; i++) {
-                InsetsSource source = other.mSources[i];
-                mSources[i] = source != null ? new InsetsSource(source) : null;
-            }
-        } else {
-            for (int i = 0; i < SIZE; i++) {
-                mSources[i] = other.mSources[i];
-            }
+        mSources.clear();
+        for (int i = 0, size = other.mSources.size(); i < size; i++) {
+            final InsetsSource otherSource = other.mSources.valueAt(i);
+            mSources.append(otherSource.getId(), copySources
+                    ? new InsetsSource(otherSource)
+                    : otherSource);
         }
     }
 
@@ -700,15 +680,25 @@
         mRoundedCornerFrame.set(other.mRoundedCornerFrame);
         mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
         mDisplayShape = other.getDisplayShape();
-        final ArraySet<Integer> t = toInternalType(types);
-        for (int i = t.size() - 1; i >= 0; i--) {
-            final int type = t.valueAt(i);
-            mSources[type] = other.mSources[type];
+        if (types == 0) {
+            return;
+        }
+        for (int i = mSources.size() - 1; i >= 0; i--) {
+            final InsetsSource source = mSources.valueAt(i);
+            if ((source.getType() & types) != 0) {
+                mSources.removeAt(i);
+            }
+        }
+        for (int i = other.mSources.size() - 1; i >= 0; i--) {
+            final InsetsSource otherSource = other.mSources.valueAt(i);
+            if ((otherSource.getType() & types) != 0) {
+                mSources.put(otherSource.getId(), otherSource);
+            }
         }
     }
 
     public void addSource(InsetsSource source) {
-        mSources[source.getId()] = source;
+        mSources.put(source.getId(), source);
     }
 
     public static boolean clearsCompatInsets(int windowType, int windowFlags, int windowingMode) {
@@ -748,15 +738,6 @@
             result.add(ITYPE_RIGHT_MANDATORY_GESTURES);
             result.add(ITYPE_BOTTOM_MANDATORY_GESTURES);
         }
-        if ((types & Type.DISPLAY_CUTOUT) != 0) {
-            result.add(ITYPE_LEFT_DISPLAY_CUTOUT);
-            result.add(ITYPE_TOP_DISPLAY_CUTOUT);
-            result.add(ITYPE_RIGHT_DISPLAY_CUTOUT);
-            result.add(ITYPE_BOTTOM_DISPLAY_CUTOUT);
-        }
-        if ((types & Type.IME) != 0) {
-            result.add(ITYPE_IME);
-        }
         return result;
     }
 
@@ -780,8 +761,6 @@
                 return Type.SYSTEM_OVERLAYS;
             case ITYPE_CAPTION_BAR:
                 return Type.CAPTION_BAR;
-            case ITYPE_IME:
-                return Type.IME;
             case ITYPE_TOP_MANDATORY_GESTURES:
             case ITYPE_BOTTOM_MANDATORY_GESTURES:
             case ITYPE_LEFT_MANDATORY_GESTURES:
@@ -797,20 +776,11 @@
             case ITYPE_RIGHT_TAPPABLE_ELEMENT:
             case ITYPE_BOTTOM_TAPPABLE_ELEMENT:
                 return Type.TAPPABLE_ELEMENT;
-            case ITYPE_LEFT_DISPLAY_CUTOUT:
-            case ITYPE_TOP_DISPLAY_CUTOUT:
-            case ITYPE_RIGHT_DISPLAY_CUTOUT:
-            case ITYPE_BOTTOM_DISPLAY_CUTOUT:
-                return Type.DISPLAY_CUTOUT;
             default:
                 throw new IllegalArgumentException("Unknown type: " + type);
         }
     }
 
-    public static boolean getDefaultVisibility(@InternalInsetsType int type) {
-        return type != ITYPE_IME;
-    }
-
     public static boolean containsType(@InternalInsetsType int[] types,
             @InternalInsetsType int type) {
         if (types == null) {
@@ -833,16 +803,14 @@
         pw.println(newPrefix + "mRoundedCornerFrame=" + mRoundedCornerFrame);
         pw.println(newPrefix + "mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds);
         pw.println(newPrefix + "mDisplayShape=" + mDisplayShape);
-        for (int i = 0; i < SIZE; i++) {
-            InsetsSource source = mSources[i];
-            if (source == null) continue;
-            source.dump(newPrefix + "  ", pw);
+        for (int i = 0, size = mSources.size(); i < size; i++) {
+            mSources.valueAt(i).dump(newPrefix + "  ", pw);
         }
     }
 
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        InsetsSource source = mSources[ITYPE_IME];
+        final InsetsSource source = mSources.get(InsetsSource.ID_IME);
         if (source != null) {
             source.dumpDebug(proto, SOURCES);
         }
@@ -883,16 +851,6 @@
                 return "ITYPE_RIGHT_TAPPABLE_ELEMENT";
             case ITYPE_BOTTOM_TAPPABLE_ELEMENT:
                 return "ITYPE_BOTTOM_TAPPABLE_ELEMENT";
-            case ITYPE_LEFT_DISPLAY_CUTOUT:
-                return "ITYPE_LEFT_DISPLAY_CUTOUT";
-            case ITYPE_TOP_DISPLAY_CUTOUT:
-                return "ITYPE_TOP_DISPLAY_CUTOUT";
-            case ITYPE_RIGHT_DISPLAY_CUTOUT:
-                return "ITYPE_RIGHT_DISPLAY_CUTOUT";
-            case ITYPE_BOTTOM_DISPLAY_CUTOUT:
-                return "ITYPE_BOTTOM_DISPLAY_CUTOUT";
-            case ITYPE_IME:
-                return "ITYPE_IME";
             case ITYPE_CLIMATE_BAR:
                 return "ITYPE_CLIMATE_BAR";
             case ITYPE_EXTRA_NAVIGATION_BAR:
@@ -921,8 +879,8 @@
      * excluded.
      * @param excludingCaptionInsets {@code true} if we want to compare two InsetsState objects but
      *                                           ignore the caption insets source value.
-     * @param excludeInvisibleImeFrames If {@link #ITYPE_IME} frames should be ignored when IME is
-     *                                  not visible.
+     * @param excludeInvisibleImeFrames If {@link WindowInsets.Type#ime()} frames should be ignored
+     *                                  when IME is not visible.
      * @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise.
      */
     @VisibleForTesting
@@ -941,38 +899,56 @@
                 || !mDisplayShape.equals(state.mDisplayShape)) {
             return false;
         }
-        for (int i = 0; i < SIZE; i++) {
-            if (excludingCaptionInsets) {
-                if (i == ITYPE_CAPTION_BAR) continue;
+
+        final SparseArray<InsetsSource> thisSources = mSources;
+        final SparseArray<InsetsSource> thatSources = state.mSources;
+        if (!excludingCaptionInsets && !excludeInvisibleImeFrames) {
+            return thisSources.contentEquals(thatSources);
+        } else {
+            final int thisSize = thisSources.size();
+            final int thatSize = thatSources.size();
+            int thisIndex = 0;
+            int thatIndex = 0;
+            while (thisIndex < thisSize && thatIndex < thatSize) {
+                // Seek to the next non-excluding source of ours.
+                InsetsSource thisSource = thisSources.valueAt(thisIndex);
+                while (thisSource != null
+                        && (excludingCaptionInsets && thisSource.getType() == captionBar()
+                                || excludeInvisibleImeFrames && thisSource.getType() == ime()
+                                        && !thisSource.isVisible())) {
+                    thisIndex++;
+                    thisSource = thisIndex < thisSize ? thisSources.valueAt(thisIndex) : null;
+                }
+
+                // Seek to the next non-excluding source of theirs.
+                InsetsSource thatSource = thatSources.valueAt(thatIndex);
+                while (thatSource != null
+                        && (excludingCaptionInsets && thatSource.getType() == captionBar()
+                                || excludeInvisibleImeFrames && thatSource.getType() == ime()
+                                        && !thatSource.isVisible())) {
+                    thatIndex++;
+                    thatSource = thatIndex < thatSize ? thatSources.valueAt(thatIndex) : null;
+                }
+
+                if (!Objects.equals(thisSource, thatSource)) {
+                    return false;
+                }
+
+                thisIndex++;
+                thatIndex++;
             }
-            InsetsSource source = mSources[i];
-            InsetsSource otherSource = state.mSources[i];
-            if (source == null && otherSource == null) {
-                continue;
-            }
-            if (excludeInvisibleImeFrames && i == ITYPE_IME
-                    && ((source == null && !otherSource.isVisible())
-                            || (otherSource == null && !source.isVisible()))) {
-                continue;
-            }
-            if (source == null || otherSource == null) {
-                return false;
-            }
-            if (!otherSource.equals(source, excludeInvisibleImeFrames)) {
-                return false;
-            }
+            return thisIndex >= thisSize && thatIndex >= thatSize;
         }
-        return true;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources),
+        return Objects.hash(mDisplayFrame, mDisplayCutout, mSources.contentHashCode(),
                 mRoundedCorners, mPrivacyIndicatorBounds, mRoundedCornerFrame, mDisplayShape);
     }
 
     public InsetsState(Parcel in) {
-        readFromParcel(in);
+        mSources = readFromParcel(in);
     }
 
     @Override
@@ -984,14 +960,18 @@
     public void writeToParcel(Parcel dest, int flags) {
         mDisplayFrame.writeToParcel(dest, flags);
         mDisplayCutout.writeToParcel(dest, flags);
-        dest.writeTypedArray(mSources, 0 /* parcelableFlags */);
         dest.writeTypedObject(mRoundedCorners, flags);
         mRoundedCornerFrame.writeToParcel(dest, flags);
         dest.writeTypedObject(mPrivacyIndicatorBounds, flags);
         dest.writeTypedObject(mDisplayShape, flags);
+        final int size = mSources.size();
+        dest.writeInt(size);
+        for (int i = 0; i < size; i++) {
+            dest.writeTypedObject(mSources.valueAt(i), flags);
+        }
     }
 
-    public static final @NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() {
+    public static final @NonNull Creator<InsetsState> CREATOR = new Creator<>() {
 
         public InsetsState createFromParcel(Parcel in) {
             return new InsetsState(in);
@@ -1002,24 +982,34 @@
         }
     };
 
-    public void readFromParcel(Parcel in) {
+    public SparseArray<InsetsSource> readFromParcel(Parcel in) {
         mDisplayFrame.readFromParcel(in);
         mDisplayCutout.readFromParcel(in);
-        in.readTypedArray(mSources, InsetsSource.CREATOR);
         mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR);
         mRoundedCornerFrame.readFromParcel(in);
         mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR);
         mDisplayShape = in.readTypedObject(DisplayShape.CREATOR);
+        final int size = in.readInt();
+        final SparseArray<InsetsSource> sources;
+        if (mSources == null) {
+            // We are constructing this InsetsState.
+            sources = new SparseArray<>(size);
+        } else {
+            sources = mSources;
+            sources.clear();
+        }
+        for (int i = 0; i < size; i++) {
+            final InsetsSource source = in.readTypedObject(InsetsSource.CREATOR);
+            sources.append(source.getId(), source);
+        }
+        return sources;
     }
 
     @Override
     public String toString() {
-        StringJoiner joiner = new StringJoiner(", ");
-        for (int i = 0; i < SIZE; i++) {
-            InsetsSource source = mSources[i];
-            if (source != null) {
-                joiner.add(source.toString());
-            }
+        final StringJoiner joiner = new StringJoiner(", ");
+        for (int i = 0, size = mSources.size(); i < size; i++) {
+            joiner.add(mSources.valueAt(i).toString());
         }
         return "InsetsState: {"
                 + "mDisplayFrame=" + mDisplayFrame
@@ -1031,5 +1021,112 @@
                 + ", mSources= { " + joiner
                 + " }";
     }
+
+    /**
+     * Traverses sources in two {@link InsetsState}s and calls back when events defined in
+     * {@link OnTraverseCallbacks} happen. This is optimized for {@link SparseArray} that we avoid
+     * triggering the binary search while getting the key or the value.
+     *
+     * This can be used to copy attributes of sources from one InsetsState to the other one, or to
+     * remove sources existing in one InsetsState but not in the other one.
+     *
+     * @param state1 The first {@link InsetsState} to be traversed.
+     * @param state2 The second {@link InsetsState} to be traversed.
+     * @param cb The {@link OnTraverseCallbacks} to call back to the caller.
+     */
+    public static void traverse(InsetsState state1, InsetsState state2, OnTraverseCallbacks cb) {
+        cb.onStart(state1, state2);
+        final int size1 = state1.sourceSize();
+        final int size2 = state2.sourceSize();
+        int index1 = 0;
+        int index2 = 0;
+        while (index1 < size1 && index2 < size2) {
+            int id1 = state1.sourceIdAt(index1);
+            int id2 = state2.sourceIdAt(index2);
+            while (id1 != id2) {
+                if (id1 < id2) {
+                    cb.onIdNotFoundInState2(index1, state1.sourceAt(index1));
+                    index1++;
+                    if (index1 < size1) {
+                        id1 = state1.sourceIdAt(index1);
+                    } else {
+                        break;
+                    }
+                } else {
+                    cb.onIdNotFoundInState1(index2, state2.sourceAt(index2));
+                    index2++;
+                    if (index2 < size2) {
+                        id2 = state2.sourceIdAt(index2);
+                    } else {
+                        break;
+                    }
+                }
+            }
+            if (index1 >= size1 || index2 >= size2) {
+                break;
+            }
+            final InsetsSource source1 = state1.sourceAt(index1);
+            final InsetsSource source2 = state2.sourceAt(index2);
+            cb.onIdMatch(source1, source2);
+            index1++;
+            index2++;
+        }
+        while (index2 < size2) {
+            cb.onIdNotFoundInState1(index2, state2.sourceAt(index2));
+            index2++;
+        }
+        while (index1 < size1) {
+            cb.onIdNotFoundInState2(index1, state1.sourceAt(index1));
+            index1++;
+        }
+        cb.onFinish(state1, state2);
+    }
+
+    /**
+     * Used with {@link #traverse(InsetsState, InsetsState, OnTraverseCallbacks)} to call back when
+     * certain events happen.
+     */
+    public interface OnTraverseCallbacks {
+
+        /**
+         * Called at the beginning of the traverse.
+         *
+         * @param state1 same as the state1 supplied to {@link #traverse}
+         * @param state2 same as the state2 supplied to {@link #traverse}
+         */
+        default void onStart(InsetsState state1, InsetsState state2) { }
+
+        /**
+         * Called when finding two IDs from two InsetsStates are the same.
+         *
+         * @param source1 the source in state1.
+         * @param source2 the source in state2.
+         */
+        default void onIdMatch(InsetsSource source1, InsetsSource source2) { }
+
+        /**
+         * Called when finding an ID in state2 but not in state1.
+         *
+         * @param index2 the index of the ID in state2.
+         * @param source2 the source which has the ID in state2.
+         */
+        default void onIdNotFoundInState1(int index2, InsetsSource source2) { }
+
+        /**
+         * Called when finding an ID in state1 but not in state2.
+         *
+         * @param index1 the index of the ID in state1.
+         * @param source1 the source which has the ID in state1.
+         */
+        default void onIdNotFoundInState2(int index1, InsetsSource source1) { }
+
+        /**
+         * Called at the end of the traverse.
+         *
+         * @param state1 same as the state1 supplied to {@link #traverse}
+         * @param state2 same as the state2 supplied to {@link #traverse}
+         */
+        default void onFinish(InsetsState state1, InsetsState state2) { }
+    }
 }
 
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 54e1a53..b003659 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -64,6 +64,7 @@
 import android.opengl.EGLSync;
 import android.os.Build;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
@@ -463,6 +464,10 @@
     public long mNativeObject;
     private long mNativeHandle;
 
+    private final Object mChoreographerLock = new Object();
+    @GuardedBy("mChoreographerLock")
+    private Choreographer mChoreographer;
+
     // TODO: Move width/height to native and fix locking through out.
     private final Object mLock = new Object();
     @GuardedBy("mLock")
@@ -1269,6 +1274,59 @@
     }
 
     /**
+     * Returns the associated {@link Choreographer} instance with the
+     * current instance of the SurfaceControl.
+     * Must be called from a thread that already has a {@link android.os.Looper}
+     * associated with it.
+     * If there is no {@link Choreographer} associated with the SurfaceControl then a new instance
+     * of the {@link Choreographer} is created.
+     *
+     * @hide
+     */
+    @TestApi
+    public @NonNull Choreographer getChoreographer() {
+        return getChoreographer(Looper.myLooper());
+    }
+
+    /**
+     * Returns the associated {@link Choreographer} instance with the
+     * current instance of the SurfaceControl.
+     * If there is no {@link Choreographer} associated with the SurfaceControl then a new instance
+     * of the {@link Choreographer} is created.
+     *
+     * @param looper the choreographer is attached on this looper
+     *
+     * @hide
+     */
+    @TestApi
+    public @NonNull Choreographer getChoreographer(@NonNull Looper looper) {
+        checkNotReleased();
+        synchronized (mChoreographerLock) {
+            if (mChoreographer != null) {
+                return mChoreographer;
+            }
+
+            mChoreographer = Choreographer.getInstanceForSurfaceControl(mNativeHandle, looper);
+            return mChoreographer;
+        }
+    }
+
+    /**
+     * Returns true if {@link Choreographer} is present otherwise false.
+     * To check the validity use {@link #isValid} on the SurfaceControl, a valid SurfaceControl with
+     * choreographer will have the valid Choreographer.
+     *
+     * @hide
+     */
+    @TestApi
+    @UnsupportedAppUsage
+    public boolean hasChoreographer() {
+        synchronized (mChoreographerLock) {
+            return mChoreographer != null;
+        }
+    }
+
+    /**
      * Write to a protocol buffer output stream. Protocol buffer message definition is at {@link
      * android.view.SurfaceControlProto}.
      *
@@ -1325,6 +1383,13 @@
             mNativeObject = 0;
             mNativeHandle = 0;
             mCloseGuard.close();
+            synchronized (mChoreographerLock) {
+                if (mChoreographer != null) {
+                    mChoreographer.invalidate();
+                    // TODO(b/266121235): Use NativeAllocationRegistry to clean up Choreographer.
+                    mChoreographer = null;
+                }
+            }
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index cb316e2..872b4f9 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -23,7 +23,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.InputDevice.SOURCE_CLASS_NONE;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.View.PFLAG_DRAW_ANIMATION;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -9040,7 +9040,7 @@
         if (mTranslator != null) {
             mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
         }
-        if (insetsState.getSourceOrDefaultVisibility(ITYPE_IME)) {
+        if (insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) {
             ImeTracing.getInstance().triggerClientDump("ViewRootImpl#dispatchResized",
                     getInsetsController().getHost().getInputMethodManager(), null /* icProto */);
         }
@@ -9072,7 +9072,7 @@
             mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
             mTranslator.translateSourceControlsInScreenToAppWindow(activeControls);
         }
-        if (insetsState != null && insetsState.getSourceOrDefaultVisibility(ITYPE_IME)) {
+        if (insetsState != null && insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) {
             ImeTracing.getInstance().triggerClientDump("ViewRootImpl#dispatchInsetsControlChanged",
                     getInsetsController().getHost().getInputMethodManager(), null /* icProto */);
         }
@@ -11511,33 +11511,25 @@
         });
     }
 
-    private class VRISurfaceSyncGroup extends SurfaceSyncGroup {
-        VRISurfaceSyncGroup(String name) {
-            super(name);
-        }
-
-        @Override
-        public void onSyncReady() {
-            Runnable runnable = () -> {
-                mNumPausedForSync--;
-                if (!mIsInTraversal && mNumPausedForSync == 0) {
-                    scheduleTraversals();
-                }
-            };
-
-            if (Thread.currentThread() == mThread) {
-                runnable.run();
-            } else {
-                mHandler.post(runnable);
-            }
-        }
-    }
-
     @Override
     public SurfaceSyncGroup getOrCreateSurfaceSyncGroup() {
         boolean newSyncGroup = false;
         if (mActiveSurfaceSyncGroup == null) {
-            mActiveSurfaceSyncGroup = new VRISurfaceSyncGroup(mTag);
+            mActiveSurfaceSyncGroup = new SurfaceSyncGroup(mTag);
+            mActiveSurfaceSyncGroup.setAddedToSyncListener(() -> {
+                Runnable runnable = () -> {
+                    mNumPausedForSync--;
+                    if (!mIsInTraversal && mNumPausedForSync == 0) {
+                        scheduleTraversals();
+                    }
+                };
+
+                if (Thread.currentThread() == mThread) {
+                    runnable.run();
+                } else {
+                    mHandler.post(runnable);
+                }
+            });
             updateSyncInProgressCount(mActiveSurfaceSyncGroup);
             newSyncGroup = true;
         }
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index 7077804..5ec5219 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -16,7 +16,7 @@
 
 package android.view;
 
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
@@ -89,7 +89,7 @@
         if (attachedWindowFrame == null) {
             outParentFrame.set(outDisplayFrame);
             if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
-                final InsetsSource source = state.peekSource(ITYPE_IME);
+                final InsetsSource source = state.peekSource(ID_IME);
                 if (source != null) {
                     outParentFrame.inset(source.calculateInsets(
                             outParentFrame, false /* ignoreVisibility */));
diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java
index 2e55041..78de954 100644
--- a/core/java/android/window/SurfaceSyncGroup.java
+++ b/core/java/android/window/SurfaceSyncGroup.java
@@ -53,7 +53,7 @@
  * see the <a href="https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/window/SurfaceSyncGroup.md">SurfaceSyncGroup documentation</a>
  * </p>
  */
-public class SurfaceSyncGroup {
+public final class SurfaceSyncGroup {
     private static final String TAG = "SurfaceSyncGroup";
     private static final boolean DEBUG = false;
 
@@ -101,6 +101,9 @@
      */
     public final ISurfaceSyncGroup mISurfaceSyncGroup = new ISurfaceSyncGroupImpl();
 
+    @GuardedBy("mLock")
+    private Runnable mAddedToSyncListener;
+
     /**
      * Token to identify this SurfaceSyncGroup. This is used to register the SurfaceSyncGroup in
      * WindowManager. This token is also sent to other processes' SurfaceSyncGroup that want to be
@@ -447,12 +450,15 @@
     }
 
     /**
-     * Invoked when the SurfaceSyncGroup has been added to another SurfaceSyncGroup and is ready
-     * to proceed.
+     * Add a Runnable to be invoked when the SurfaceSyncGroup has been added to another
+     * SurfaceSyncGroup. This is useful to know when it's safe to proceed rendering.
      *
      * @hide
      */
-    public void onSyncReady() {
+    public void setAddedToSyncListener(Runnable addedToSyncListener) {
+        synchronized (mLock) {
+            mAddedToSyncListener = addedToSyncListener;
+        }
     }
 
     private boolean addSyncToWm(IBinder token, boolean parentSyncGroupMerge,
@@ -525,12 +531,15 @@
         if (DEBUG) {
             Log.d(TAG, "setTransactionCallbackFromParent " + mName);
         }
-        boolean finished = false;
+
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
             Trace.traceBegin(Trace.TRACE_TAG_VIEW,
                     "setTransactionCallbackFromParent " + mName + " callback="
                             + transactionReadyCallback.hashCode());
         }
+
+        boolean finished = false;
+        Runnable addedToSyncListener = null;
         synchronized (mLock) {
             if (mFinished) {
                 finished = true;
@@ -577,6 +586,7 @@
                         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
                     }
                 };
+                addedToSyncListener = mAddedToSyncListener;
             }
         }
 
@@ -587,8 +597,8 @@
                 transactionReadyCallback.onTransactionReady(null);
             } catch (RemoteException e) {
             }
-        } else {
-            onSyncReady();
+        } else if (addedToSyncListener != null) {
+            addedToSyncListener.run();
         }
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index c497c94..3478b0f 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -127,7 +127,7 @@
                     isScreenRound, alwaysConsumeSystemBars, SOFT_INPUT_ADJUST_NOTHING,
                     0 /* flags */, SYSTEM_UI_FLAG_VISIBLE,
                     WindowManager.LayoutParams.INVALID_WINDOW_TYPE, windowingMode,
-                    null /* typeSideMap */);
+                    null /* idSideMap */);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index b9373be..1084c71 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -562,6 +562,13 @@
             "task_manager_show_user_visible_jobs";
 
     /**
+     * (boolean) Whether the task manager should tell JobScheduler it's about to ask for an
+     * app stop.
+     */
+    public static final String TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP =
+            "task_manager_inform_job_scheduler_of_pending_app_stop";
+
+    /**
      * (boolean) Whether to show notification volume control slider separate from ring.
      */
     public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification";
diff --git a/core/java/com/android/internal/statusbar/AppClipsServiceConnector.java b/core/java/com/android/internal/statusbar/AppClipsServiceConnector.java
new file mode 100644
index 0000000..287b85f
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/AppClipsServiceConnector.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.statusbar;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * A helper class to communicate with the App Clips service running in SystemUI.
+ */
+public class AppClipsServiceConnector {
+
+    private static final String TAG = AppClipsServiceConnector.class.getSimpleName();
+
+    private final Context mContext;
+    private final Handler mHandler;
+
+    public AppClipsServiceConnector(Context context) {
+        mContext = context;
+        HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        mHandler = handlerThread.getThreadHandler();
+    }
+
+    /**
+     * @return true if the task represented by {@code taskId} can launch App Clips screenshot flow,
+     * false otherwise.
+     */
+    public boolean canLaunchCaptureContentActivityForNote(int taskId) {
+        try {
+            CompletableFuture<Boolean> future = new CompletableFuture<>();
+            connectToServiceAndProcessRequest(taskId, future);
+            return future.get();
+        } catch (Exception e) {
+            Log.d(TAG, "Exception from service\n" + e);
+        }
+
+        return false;
+    }
+
+    private void connectToServiceAndProcessRequest(int taskId, CompletableFuture<Boolean> future) {
+        ServiceConnection serviceConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                try {
+                    future.complete(IAppClipsService.Stub.asInterface(
+                            service).canLaunchCaptureContentActivityForNote(taskId));
+                } catch (Exception e) {
+                    Log.d(TAG, "Exception from service\n" + e);
+                }
+                future.complete(false);
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                if (!future.isDone()) {
+                    future.complete(false);
+                }
+            }
+        };
+
+        final ComponentName serviceComponent = ComponentName.unflattenFromString(
+                mContext.getResources().getString(
+                        com.android.internal.R.string.config_screenshotAppClipsServiceComponent));
+        final Intent serviceIntent = new Intent();
+        serviceIntent.setComponent(serviceComponent);
+
+        boolean bindService = mContext.bindServiceAsUser(serviceIntent, serviceConnection,
+                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, mHandler,
+                mContext.getUser());
+
+        // Complete the future early if service not bound.
+        if (!bindService) {
+            future.complete(false);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/statusbar/IAppClipsService.aidl b/core/java/com/android/internal/statusbar/IAppClipsService.aidl
new file mode 100644
index 0000000..013d0d3
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/IAppClipsService.aidl
@@ -0,0 +1,26 @@
+/**
+ * Copyright (C) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.statusbar;
+
+/**
+ * A service that runs in SystemUI and helps determine if App Clips flow is supported in the
+ * current state of device. This service needs to run in SystemUI in order to communicate with the
+ * instance of app bubbles.
+ */
+interface IAppClipsService {
+    boolean canLaunchCaptureContentActivityForNote(in int taskId);
+}
\ No newline at end of file
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index a8d8a43..8855b78 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -65,7 +65,7 @@
 public:
     NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
                                const sp<MessageQueue>& messageQueue, jint vsyncSource,
-                               jint eventRegistration);
+                               jint eventRegistration, jlong layerHandle);
 
     void dispose();
 
@@ -88,11 +88,15 @@
 
 NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak,
                                                        const sp<MessageQueue>& messageQueue,
-                                                       jint vsyncSource, jint eventRegistration)
+                                                       jint vsyncSource, jint eventRegistration,
+                                                       jlong layerHandle)
       : DisplayEventDispatcher(messageQueue->getLooper(),
                                static_cast<gui::ISurfaceComposer::VsyncSource>(vsyncSource),
                                static_cast<gui::ISurfaceComposer::EventRegistration>(
-                                       eventRegistration)),
+                                       eventRegistration),
+                               layerHandle != 0 ? sp<IBinder>::fromExisting(
+                                                          reinterpret_cast<IBinder*>(layerHandle))
+                                                : nullptr),
         mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)),
         mMessageQueue(messageQueue) {
     ALOGV("receiver %p ~ Initializing display event receiver.", this);
@@ -214,7 +218,7 @@
 }
 
 static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject messageQueueObj,
-                        jint vsyncSource, jint eventRegistration) {
+                        jint vsyncSource, jint eventRegistration, jlong layerHandle) {
     sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
     if (messageQueue == NULL) {
         jniThrowRuntimeException(env, "MessageQueue is not initialized.");
@@ -223,7 +227,7 @@
 
     sp<NativeDisplayEventReceiver> receiver =
             new NativeDisplayEventReceiver(env, receiverWeak, messageQueue, vsyncSource,
-                                           eventRegistration);
+                                           eventRegistration, layerHandle);
     status_t status = receiver->initialize();
     if (status) {
         String8 message;
@@ -268,7 +272,7 @@
 
 static const JNINativeMethod gMethods[] = {
         /* name, signature, funcPtr */
-        {"nativeInit", "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;II)J",
+        {"nativeInit", "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;IIJ)J",
          (void*)nativeInit},
         {"nativeDispose", "(J)V", (void*)nativeDispose},
         // @FastNative
diff --git a/core/proto/android/service/usb.proto b/core/proto/android/service/usb.proto
index 607fd10..c98e346 100644
--- a/core/proto/android/service/usb.proto
+++ b/core/proto/android/service/usb.proto
@@ -284,7 +284,8 @@
 
     optional int32 cards_parser = 1;
     repeated UsbAlsaDeviceProto alsa_devices = 2;
-    repeated UsbMidiDeviceProto midi_devices = 3;
+    reserved 3; // previously midi_devices, now unused
+    repeated UsbAlsaMidiDeviceProto alsa_midi_devices = 4;
 }
 
 message UsbAlsaDeviceProto {
@@ -299,7 +300,7 @@
     optional string address = 6;
 }
 
-message UsbMidiDeviceProto {
+message UsbAlsaMidiDeviceProto {
     option (android.msg_privacy).dest = DEST_AUTOMATIC;
 
     optional int32 card = 1;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 49f4d35..6aee3cd 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2579,6 +2579,14 @@
     <!-- ==================================================== -->
     <eat-comment />
 
+    <!-- Allows an application to capture screen content to perform a screenshot using the intent
+         action {@link android.content.Intent#ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE}.
+         <p>Protection level: internal|role
+         <p>Intended for use by ROLE_NOTES only.
+    -->
+    <permission android:name="android.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE"
+        android:protectionLevel="internal|role" />
+
     <!-- Allows an application to get notified when a screen capture of its windows is attempted.
          <p>Protection level: normal
     -->
@@ -6921,8 +6929,8 @@
 
     <!-- Allows an assistive application to perform actions on behalf of users inside of
          applications.
-         <p>For now, this permission is only granted to system applications fulfilling the
-         ASSISTANT role.
+         <p>For now, this permission is only granted to the Assistant application selected by
+         the user.
          <p>Protection level: internal|role
     -->
     <permission android:name="android.permission.EXECUTE_APP_ACTION"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1e3074c..0c13484 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3233,6 +3233,12 @@
     <string name="config_somnambulatorComponent" translatable="false"
             >com.android.systemui/com.android.systemui.Somnambulator</string>
 
+    <!-- The component name of the screenshot App Clips service that communicates with SystemUI to
+         evaluate certain aspects of App Clips flow such as whether a calling activity can launch
+         capture content for note activity. -->
+    <string name="config_screenshotAppClipsServiceComponent" translatable="false"
+            >com.android.systemui/com.android.systemui.screenshot.appclips.AppClipsService</string>
+
     <!-- The component name of a special dock app that merely launches a dream.
          We don't want to launch this app when docked because it causes an unnecessary
          activity transition.  We just want to start the dream.. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 11aac16..18084d8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -370,6 +370,7 @@
   <java-symbol type="string" name="config_controlsPackage" />
   <java-symbol type="string" name="config_screenRecorderComponent" />
   <java-symbol type="string" name="config_somnambulatorComponent" />
+  <java-symbol type="string" name="config_screenshotAppClipsServiceComponent" />
   <java-symbol type="string" name="config_screenshotServiceComponent" />
   <java-symbol type="string" name="config_screenshotErrorReceiverComponent" />
   <java-symbol type="string" name="config_slicePermissionComponent" />
diff --git a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
index b1991c2..b3c7f88 100644
--- a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
+++ b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
@@ -60,8 +60,8 @@
 
     public void timeReadWriteInsetsState(int reps) {
         final InsetsState insetsState = new InsetsState();
-        for (int i = 0; i < InsetsState.SIZE; i++) {
-            insetsState.addSource(new InsetsSource(i, InsetsState.toPublicType(i)));
+        for (int i = 0; i < 10; i++) {
+            insetsState.addSource(new InsetsSource(i, 1 << i));
         }
         for (int i = 0; i < reps; i++) {
             insetsState.writeToParcel(mParcel, 0);
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 958fdc6..a3eda8d 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -17,7 +17,7 @@
 package android.view;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
@@ -92,7 +92,7 @@
 
     @Test
     public void testImeVisibility() {
-        final InsetsSourceControl ime = new InsetsSourceControl(ITYPE_IME, WindowInsets.Type.ime(),
+        final InsetsSourceControl ime = new InsetsSourceControl(ID_IME, WindowInsets.Type.ime(),
                 mLeash, false, new Point(), Insets.NONE);
         mController.onControlsChanged(new InsetsSourceControl[] { ime });
 
@@ -121,7 +121,7 @@
             mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
 
             // set control and verify visibility is applied.
-            InsetsSourceControl control = new InsetsSourceControl(ITYPE_IME,
+            InsetsSourceControl control = new InsetsSourceControl(ID_IME,
                     WindowInsets.Type.ime(), mLeash, false, new Point(), Insets.NONE);
             mController.onControlsChanged(new InsetsSourceControl[] { control });
             // IME show animation should be triggered when control becomes available.
@@ -161,7 +161,7 @@
             }
 
             // set control and verify visibility is applied.
-            InsetsSourceControl control = Mockito.spy(new InsetsSourceControl(ITYPE_IME,
+            InsetsSourceControl control = Mockito.spy(new InsetsSourceControl(ID_IME,
                     WindowInsets.Type.ime(), mLeash, false, new Point(), Insets.NONE));
             // Simulate IME source control set this flag when the target has starting window.
             control.setSkipAnimationOnce(true);
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index cc5f7f8..1682135 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -16,8 +16,8 @@
 
 package android.view;
 
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowInsets.Type.systemBars;
 
 import static org.junit.Assert.assertEquals;
@@ -66,6 +66,11 @@
 @RunWith(AndroidJUnit4.class)
 public class InsetsAnimationControlImplTest {
 
+    private static final int ID_STATUS_BAR = InsetsSource.createId(
+            null /* owner */, 0 /* index */, statusBars());
+    private static final int ID_NAVIGATION_BAR = InsetsSource.createId(
+            null /* owner */, 0 /* index */, navigationBars());
+
     private InsetsAnimationControlImpl mController;
 
     private SurfaceSession mSession = new SurfaceSession();
@@ -87,29 +92,31 @@
                 .setName("testSurface")
                 .build();
         mInsetsState = new InsetsState();
-        mInsetsState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 500, 100));
-        mInsetsState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(400, 0, 500, 500));
-        InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR,
+        mInsetsState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 500, 100));
+        mInsetsState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars())
+                .setFrame(new Rect(400, 0, 500, 500));
+        InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ID_STATUS_BAR,
                 WindowInsets.Type.statusBars(), mInsetsState,
                 () -> mMockTransaction, mMockController);
         topConsumer.setControl(
-                new InsetsSourceControl(ITYPE_STATUS_BAR, WindowInsets.Type.statusBars(),
+                new InsetsSourceControl(ID_STATUS_BAR, WindowInsets.Type.statusBars(),
                         mStatusLeash, true, new Point(0, 0), Insets.of(0, 100, 0, 0)),
                 new int[1], new int[1]);
 
-        InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ITYPE_NAVIGATION_BAR,
+        InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ID_NAVIGATION_BAR,
                 WindowInsets.Type.navigationBars(), mInsetsState,
                 () -> mMockTransaction, mMockController);
         navConsumer.setControl(
-                new InsetsSourceControl(ITYPE_NAVIGATION_BAR, WindowInsets.Type.navigationBars(),
+                new InsetsSourceControl(ID_NAVIGATION_BAR, WindowInsets.Type.navigationBars(),
                         mNavLeash, true, new Point(400, 0), Insets.of(0, 0, 100, 0)),
                 new int[1], new int[1]);
         mMockController.setRequestedVisibleTypes(0, WindowInsets.Type.navigationBars());
         navConsumer.applyLocalVisibilityOverride();
 
         SparseArray<InsetsSourceControl> controls = new SparseArray<>();
-        controls.put(ITYPE_STATUS_BAR, topConsumer.getControl());
-        controls.put(ITYPE_NAVIGATION_BAR, navConsumer.getControl());
+        controls.put(ID_STATUS_BAR, topConsumer.getControl());
+        controls.put(ID_NAVIGATION_BAR, navConsumer.getControl());
         mController = new InsetsAnimationControlImpl(controls,
                 new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
                 mMockController, 10 /* durationMs */, new LinearInterpolator(),
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index 5ec93e5..ca1367a 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -22,16 +22,13 @@
 import static android.view.InsetsController.ANIMATION_TYPE_RESIZE;
 import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
 import static android.view.InsetsController.AnimationType;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.InsetsSourceConsumer.ShowResult.IME_SHOW_DELAYED;
 import static android.view.InsetsSourceConsumer.ShowResult.SHOW_IMMEDIATELY;
-import static android.view.InsetsState.FIRST_TYPE;
-import static android.view.InsetsState.ITYPE_CAPTION_BAR;
-import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.LAST_TYPE;
 import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
+import static android.view.WindowInsets.Type.SIZE;
 import static android.view.WindowInsets.Type.all;
+import static android.view.WindowInsets.Type.captionBar;
 import static android.view.WindowInsets.Type.defaultVisible;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.navigationBars;
@@ -64,7 +61,6 @@
 import android.graphics.Rect;
 import android.os.CancellationSignal;
 import android.platform.test.annotations.Presubmit;
-import android.view.InsetsState.InternalInsetsType;
 import android.view.SurfaceControl.Transaction;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.OnControllableInsetsChangedListener;
@@ -101,6 +97,12 @@
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class InsetsControllerTest {
+
+    private static final int ID_STATUS_BAR = InsetsSource.createId(
+            null /* owner */, 0 /* index */, statusBars());
+    private static final int ID_NAVIGATION_BAR = InsetsSource.createId(
+            null /* owner */, 0 /* index */, navigationBars());
+
     private InsetsSource mStatusSource;
     private InsetsSource mNavSource;
     private InsetsSource mImeSource;
@@ -153,11 +155,11 @@
                 }
             }, mTestHandler);
             final Rect rect = new Rect(5, 5, 5, 5);
-            mStatusSource = new InsetsSource(ITYPE_STATUS_BAR, statusBars());
+            mStatusSource = new InsetsSource(ID_STATUS_BAR, statusBars());
             mStatusSource.setFrame(new Rect(0, 0, 100, 10));
-            mNavSource = new InsetsSource(ITYPE_NAVIGATION_BAR, navigationBars());
+            mNavSource = new InsetsSource(ID_NAVIGATION_BAR, navigationBars());
             mNavSource.setFrame(new Rect(0, 90, 100, 100));
-            mImeSource = new InsetsSource(ITYPE_IME, ime());
+            mImeSource = new InsetsSource(ID_IME, ime());
             mImeSource.setFrame(new Rect(0, 0, 100, 10));
             InsetsState state = new InsetsState();
             state.addSource(mStatusSource);
@@ -179,7 +181,7 @@
 
     @Test
     public void testControlsChanged() {
-        mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
+        mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
         assertNotNull(mController.getSourceConsumer(mStatusSource).getControl().getLeash());
         mController.addOnControllableInsetsChangedListener(
                 ((controller, typeMask) -> assertEquals(statusBars(), typeMask)));
@@ -190,7 +192,7 @@
         OnControllableInsetsChangedListener listener
                 = mock(OnControllableInsetsChangedListener.class);
         mController.addOnControllableInsetsChangedListener(listener);
-        mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
+        mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
         mController.onControlsChanged(new InsetsSourceControl[0]);
         assertNull(mController.getSourceConsumer(mStatusSource).getControl());
         InOrder inOrder = Mockito.inOrder(listener);
@@ -202,7 +204,7 @@
     @Test
     public void testControlsRevoked_duringAnim() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
+            mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
 
             ArgumentCaptor<WindowInsetsAnimationController> animationController =
                     ArgumentCaptor.forClass(WindowInsetsAnimationController.class);
@@ -231,7 +233,7 @@
 
             InsetsSourceControl control =
                     new InsetsSourceControl(
-                            ITYPE_STATUS_BAR, statusBars(), mLeash, true, new Point(),
+                            ID_STATUS_BAR, statusBars(), mLeash, true, new Point(),
                             Insets.of(0, 10, 0, 0));
             mController.onControlsChanged(new InsetsSourceControl[]{control});
             mController.controlWindowInsetsAnimation(0, 0 /* durationMs */,
@@ -284,7 +286,7 @@
 
     @Test
     public void testApplyImeVisibility() {
-        InsetsSourceControl ime = createControl(ITYPE_IME, ime());
+        InsetsSourceControl ime = createControl(ID_IME, ime());
         mController.onControlsChanged(new InsetsSourceControl[] { ime });
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.getSourceConsumer(mImeSource).onWindowFocusGained(true);
@@ -423,28 +425,28 @@
 
     @Test
     public void testRestoreStartsAnimation() {
-        mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
+        mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.hide(statusBars());
             mController.cancelExistingAnimations();
             assertFalse(isRequestedVisible(mController, statusBars()));
-            assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
+            assertFalse(mController.getState().peekSource(ID_STATUS_BAR).isVisible());
 
             // Loosing control
             InsetsState state = new InsetsState(mController.getState());
-            state.setSourceVisible(ITYPE_STATUS_BAR, true);
+            state.setSourceVisible(ID_STATUS_BAR, true);
             mController.onStateChanged(state);
             mController.onControlsChanged(new InsetsSourceControl[0]);
             assertFalse(isRequestedVisible(mController, statusBars()));
-            assertTrue(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
+            assertTrue(mController.getState().peekSource(ID_STATUS_BAR).isVisible());
 
             // Gaining control
-            mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
+            mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
             assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(statusBars()));
             mController.cancelExistingAnimations();
             assertFalse(isRequestedVisible(mController, statusBars()));
-            assertFalse(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
+            assertFalse(mController.getState().peekSource(ID_STATUS_BAR).isVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -455,18 +457,18 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
 
             mController.show(ime());
-            assertFalse(mController.getState().getSource(ITYPE_IME).isVisible());
+            assertFalse(mController.getState().peekSource(ID_IME).isVisible());
 
             // Pretend IME is calling
             mController.show(ime(), true /* fromIme */, null /* statsToken */);
 
             // Gaining control shortly after
-            mController.onControlsChanged(createSingletonControl(ITYPE_IME, ime()));
+            mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
 
             assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
             mController.cancelExistingAnimations();
             assertTrue(isRequestedVisible(mController, ime()));
-            assertTrue(mController.getState().getSource(ITYPE_IME).isVisible());
+            assertTrue(mController.getState().peekSource(ID_IME).isVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -476,10 +478,10 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
 
             mController.show(ime());
-            assertFalse(mController.getState().getSource(ITYPE_IME).isVisible());
+            assertFalse(mController.getState().peekSource(ID_IME).isVisible());
 
             // Gaining control shortly after
-            mController.onControlsChanged(createSingletonControl(ITYPE_IME, ime()));
+            mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
 
             // Pretend IME is calling
             mController.show(ime(), true /* fromIme */, null /* statsToken */);
@@ -487,14 +489,14 @@
             assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
             mController.cancelExistingAnimations();
             assertTrue(isRequestedVisible(mController, ime()));
-            assertTrue(mController.getState().getSource(ITYPE_IME).isVisible());
+            assertTrue(mController.getState().peekSource(ID_IME).isVisible());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
 
     @Test
     public void testAnimationEndState_controller() throws Exception {
-        mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
+        mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             WindowInsetsAnimationControlListener mockListener =
@@ -520,7 +522,7 @@
 
     @Test
     public void testCancellation_afterGainingControl() throws Exception {
-        mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
+        mController.onControlsChanged(createSingletonControl(ID_STATUS_BAR, statusBars()));
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             WindowInsetsAnimationControlListener mockListener =
@@ -641,61 +643,60 @@
     public void testFrameUpdateDuringAnimation() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
 
-            mController.onControlsChanged(createSingletonControl(ITYPE_IME, ime()));
+            mController.onControlsChanged(createSingletonControl(ID_IME, ime()));
 
             // Pretend IME is calling
             mController.show(ime(), true /* fromIme */, null /* statsToken */);
 
             InsetsState copy = new InsetsState(mController.getState(), true /* copySources */);
-            copy.getSource(ITYPE_IME).setFrame(0, 1, 2, 3);
-            copy.getSource(ITYPE_IME).setVisibleFrame(new Rect(4, 5, 6, 7));
+            copy.peekSource(ID_IME).setFrame(0, 1, 2, 3);
+            copy.peekSource(ID_IME).setVisibleFrame(new Rect(4, 5, 6, 7));
             mController.onStateChanged(copy);
             assertNotEquals(new Rect(0, 1, 2, 3),
-                    mController.getState().getSource(ITYPE_IME).getFrame());
+                    mController.getState().peekSource(ID_IME).getFrame());
             assertNotEquals(new Rect(4, 5, 6, 7),
-                    mController.getState().getSource(ITYPE_IME).getVisibleFrame());
+                    mController.getState().peekSource(ID_IME).getVisibleFrame());
             mController.cancelExistingAnimations();
             assertEquals(new Rect(0, 1, 2, 3),
-                    mController.getState().getSource(ITYPE_IME).getFrame());
+                    mController.getState().peekSource(ID_IME).getFrame());
             assertEquals(new Rect(4, 5, 6, 7),
-                    mController.getState().getSource(ITYPE_IME).getVisibleFrame());
+                    mController.getState().peekSource(ID_IME).getVisibleFrame());
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
 
     @Test
     public void testResizeAnimation_insetsTypes() {
-        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            final @AnimationType int expectedAnimationType =
-                    (InsetsState.toPublicType(type) & systemBars()) != 0
+        for (int i = 0; i < SIZE; i++) {
+            final @InsetsType int type = 1 << i;
+            final @AnimationType int expectedAnimationType = (type & systemBars()) != 0
                             ? ANIMATION_TYPE_RESIZE
                             : ANIMATION_TYPE_NONE;
             doTestResizeAnimation_insetsTypes(type, expectedAnimationType);
         }
     }
 
-    private void doTestResizeAnimation_insetsTypes(@InternalInsetsType int type,
+    private void doTestResizeAnimation_insetsTypes(@InsetsType int type,
             @AnimationType int expectedAnimationType) {
-        final @InsetsType int publicType = InsetsState.toPublicType(type);
+        final int id = type;
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             final InsetsState state1 = new InsetsState();
-            state1.getSource(type).setVisible(true);
-            state1.getSource(type).setFrame(0, 0, 500, 50);
+            state1.getOrCreateSource(id, type).setVisible(true).setFrame(0, 0, 500, 50);
             final InsetsState state2 = new InsetsState(state1, true /* copySources */);
-            state2.getSource(type).setFrame(0, 0, 500, 60);
-            final String message = "Animation type of " + InsetsState.typeToString(type) + ":";
+            state2.peekSource(id).setFrame(0, 0, 500, 60);
+            final String message = "Animation type of " + WindowInsets.Type.toString(type) + ":";
 
             // New insets source won't cause the resize animation.
             mController.onStateChanged(state1);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
 
             // Changing frame might cause the resize animation. This depends on the insets type.
             mController.onStateChanged(state2);
-            assertEquals(message, expectedAnimationType, mController.getAnimationType(publicType));
+            assertEquals(message, expectedAnimationType, mController.getAnimationType(type));
 
             // Cancel the existing animations for the next iteration.
             mController.cancelExistingAnimations();
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -703,23 +704,23 @@
     @Test
     public void testResizeAnimation_displayFrame() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            final @InternalInsetsType int type = ITYPE_STATUS_BAR;
-            final @InsetsType int publicType = statusBars();
+            final int id = ID_STATUS_BAR;
+            final @InsetsType int type = statusBars();
             final InsetsState state1 = new InsetsState();
             state1.setDisplayFrame(new Rect(0, 0, 500, 1000));
-            state1.getSource(type).setFrame(0, 0, 500, 50);
+            state1.getOrCreateSource(id, type).setFrame(0, 0, 500, 50);
             final InsetsState state2 = new InsetsState(state1, true /* copySources */);
             state2.setDisplayFrame(new Rect(0, 0, 500, 1010));
-            state2.getSource(type).setFrame(0, 0, 500, 60);
+            state2.peekSource(id).setFrame(0, 0, 500, 60);
             final String message = "There must not be resize animation.";
 
             // New insets source won't cause the resize animation.
             mController.onStateChanged(state1);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
 
             // Changing frame won't cause the resize animation if the display frame is also changed.
             mController.onStateChanged(state2);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -727,32 +728,29 @@
     @Test
     public void testResizeAnimation_visibility() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            final @InternalInsetsType int type = ITYPE_STATUS_BAR;
-            final @InsetsType int publicType = statusBars();
+            final int id = ID_STATUS_BAR;
+            final @InsetsType int type = statusBars();
             final InsetsState state1 = new InsetsState();
-            state1.getSource(type).setVisible(true);
-            state1.getSource(type).setFrame(0, 0, 500, 50);
+            state1.getOrCreateSource(id, type).setVisible(true).setFrame(0, 0, 500, 50);
             final InsetsState state2 = new InsetsState(state1, true /* copySources */);
-            state2.getSource(type).setVisible(false);
-            state2.getSource(type).setFrame(0, 0, 500, 60);
+            state2.peekSource(id).setVisible(false).setFrame(0, 0, 500, 60);
             final InsetsState state3 = new InsetsState(state2, true /* copySources */);
-            state3.getSource(type).setVisible(true);
-            state3.getSource(type).setFrame(0, 0, 500, 70);
+            state3.peekSource(id).setVisible(true).setFrame(0, 0, 500, 70);
             final String message = "There must not be resize animation.";
 
             // New insets source won't cause the resize animation.
             mController.onStateChanged(state1);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
 
             // Changing source visibility (visible --> invisible) won't cause the resize animation.
             // The previous source and the current one must be both visible.
             mController.onStateChanged(state2);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
 
             // Changing source visibility (invisible --> visible) won't cause the resize animation.
             // The previous source and the current one must be both visible.
             mController.onStateChanged(state3);
-            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(publicType));
+            assertEquals(message, ANIMATION_TYPE_NONE, mController.getAnimationType(type));
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -765,22 +763,20 @@
             return;
         }
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            mController.onFrameChanged(new Rect(0, 0, 100, 300));
-            final InsetsState state = new InsetsState(mController.getState(), true);
-            final Rect captionFrame = new Rect(0, 0, 100, 100);
-            mController.setCaptionInsetsHeight(100);
-            mController.onStateChanged(state);
-            final InsetsState currentState = new InsetsState(mController.getState());
-            // The caption bar source should be synced with the info in mAttachInfo.
-            assertEquals(captionFrame, currentState.peekSource(ITYPE_CAPTION_BAR).getFrame());
-            assertTrue(currentState.equals(state, true /* excludingCaptionInsets*/,
-                    true /* excludeInvisibleIme */));
+            final Rect frame = new Rect(0, 0, 100, 300);
+            final int captionBarHeight = 100;
+            final InsetsState state = mController.getState();
+            mController.onFrameChanged(frame);
+            mController.setCaptionInsetsHeight(captionBarHeight);
+            // The caption bar insets height should be the same as the caption bar height.
+            assertEquals(captionBarHeight, state.calculateInsets(frame, captionBar(), false).top);
             // Test update to remove the caption bar
             mController.setCaptionInsetsHeight(0);
-            mController.onStateChanged(state);
             // The caption bar source should not be there at all, because we don't add empty
             // caption to the state from the server.
-            assertNull(mController.getState().peekSource(ITYPE_CAPTION_BAR));
+            for (int i = state.sourceSize() - 1; i >= 0; i--) {
+                assertNotEquals(captionBar(), state.sourceAt(i).getType());
+            }
         });
     }
 
@@ -792,7 +788,6 @@
             return;
         }
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            final InsetsState state = new InsetsState(mController.getState(), true);
             reset(mTestHost);
             mController.setCaptionInsetsHeight(100);
             verify(mTestHost).notifyInsetsChanged();
@@ -881,28 +876,28 @@
             // Changing status bar frame should cause notifyInsetsChanged.
             clearInvocations(mTestHost);
             InsetsState newState = new InsetsState(localState, true /* copySources */);
-            newState.getSource(ITYPE_STATUS_BAR).getFrame().bottom++;
+            newState.peekSource(ID_STATUS_BAR).getFrame().bottom++;
             mController.onStateChanged(newState);
             verify(mTestHost, times(1)).notifyInsetsChanged();
 
             // Changing status bar visibility should cause notifyInsetsChanged.
             clearInvocations(mTestHost);
             newState = new InsetsState(localState, true /* copySources */);
-            newState.getSource(ITYPE_STATUS_BAR).setVisible(false);
+            newState.peekSource(ID_STATUS_BAR).setVisible(false);
             mController.onStateChanged(newState);
             verify(mTestHost, times(1)).notifyInsetsChanged();
 
             // Changing invisible IME frame should not cause notifyInsetsChanged.
             clearInvocations(mTestHost);
             newState = new InsetsState(localState, true /* copySources */);
-            newState.getSource(ITYPE_IME).getFrame().top--;
+            newState.peekSource(ID_IME).getFrame().top--;
             mController.onStateChanged(newState);
             verify(mTestHost, never()).notifyInsetsChanged();
 
             // Changing IME visibility should cause notifyInsetsChanged.
             clearInvocations(mTestHost);
             newState = new InsetsState(localState, true /* copySources */);
-            newState.getSource(ITYPE_IME).setVisible(true);
+            newState.peekSource(ID_IME).setVisible(true);
             mController.onStateChanged(newState);
             verify(mTestHost, times(1)).notifyInsetsChanged();
         });
@@ -947,9 +942,9 @@
     }
 
     private InsetsSourceControl[] prepareControls() {
-        final InsetsSourceControl navBar = createControl(ITYPE_NAVIGATION_BAR, navigationBars());
-        final InsetsSourceControl statusBar = createControl(ITYPE_STATUS_BAR, statusBars());
-        final InsetsSourceControl ime = createControl(ITYPE_IME, ime());
+        final InsetsSourceControl navBar = createControl(ID_NAVIGATION_BAR, navigationBars());
+        final InsetsSourceControl statusBar = createControl(ID_STATUS_BAR, statusBars());
+        final InsetsSourceControl ime = createControl(ID_IME, ime());
 
         InsetsSourceControl[] controls = new InsetsSourceControl[3];
         controls[0] = navBar;
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 0486e3c..988e690 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -18,9 +18,8 @@
 
 import static android.view.InsetsController.ANIMATION_TYPE_NONE;
 import static android.view.InsetsController.ANIMATION_TYPE_USER;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.InsetsSourceConsumer.ShowResult.SHOW_IMMEDIATELY;
-import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.statusBars;
 
@@ -69,6 +68,9 @@
 @RunWith(AndroidJUnit4.class)
 public class InsetsSourceConsumerTest {
 
+    private static final int ID_STATUS_BAR = InsetsSource.createId(
+            null /* owner */, 0 /* index */, statusBars());
+
     private InsetsSourceConsumer mConsumer;
 
     private SurfaceSession mSession = new SurfaceSession();
@@ -97,11 +99,11 @@
                 // activity isn't running, lets ignore BadTokenException.
             }
             mState = new InsetsState();
-            mSpyInsetsSource = Mockito.spy(new InsetsSource(ITYPE_STATUS_BAR, statusBars()));
+            mSpyInsetsSource = Mockito.spy(new InsetsSource(ID_STATUS_BAR, statusBars()));
             mState.addSource(mSpyInsetsSource);
 
             mController = new InsetsController(new ViewRootInsetsControllerHost(mViewRoot));
-            mConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, statusBars(), mState,
+            mConsumer = new InsetsSourceConsumer(ID_STATUS_BAR, statusBars(), mState,
                     () -> mMockTransaction, mController) {
                 @Override
                 public void removeSurface() {
@@ -113,7 +115,7 @@
         instrumentation.waitForIdleSync();
 
         mConsumer.setControl(
-                new InsetsSourceControl(ITYPE_STATUS_BAR, statusBars(), mLeash,
+                new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash,
                         true /* initialVisible */, new Point(), Insets.NONE),
                 new int[1], new int[1]);
     }
@@ -147,25 +149,25 @@
         InsetsController controller = new InsetsController(new ViewRootInsetsControllerHost(
                 mViewRoot));
         InsetsSourceConsumer consumer = new InsetsSourceConsumer(
-                ITYPE_IME, ime(), state, null, controller);
+                ID_IME, ime(), state, null, controller);
 
-        InsetsSource source = new InsetsSource(ITYPE_IME, ime());
+        InsetsSource source = new InsetsSource(ID_IME, ime());
         source.setFrame(0, 1, 2, 3);
         consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_NONE);
 
         // While we're animating, updates are delayed
         source.setFrame(4, 5, 6, 7);
         consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER);
-        assertEquals(new Rect(0, 1, 2, 3), state.peekSource(ITYPE_IME).getFrame());
+        assertEquals(new Rect(0, 1, 2, 3), state.peekSource(ID_IME).getFrame());
 
         // Finish the animation, now the pending frame should be applied
         assertTrue(consumer.onAnimationStateChanged(false /* running */));
-        assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame());
+        assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ID_IME).getFrame());
 
         // Animating again, updates are delayed
         source.setFrame(8, 9, 10, 11);
         consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER);
-        assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame());
+        assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ID_IME).getFrame());
 
         // Updating with the current frame triggers a different code path, verify this clears
         // the pending 8, 9, 10, 11 frame:
@@ -173,7 +175,7 @@
         consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER);
 
         assertFalse(consumer.onAnimationStateChanged(false /* running */));
-        assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame());
+        assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ID_IME).getFrame());
     }
 
     @Test
@@ -185,7 +187,7 @@
             verifyZeroInteractions(mMockTransaction);
             int[] hideTypes = new int[1];
             mConsumer.setControl(
-                    new InsetsSourceControl(ITYPE_STATUS_BAR, statusBars(), mLeash,
+                    new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash,
                             true /* initialVisible */, new Point(), Insets.NONE),
                     new int[1], hideTypes);
             assertEquals(statusBars(), hideTypes[0]);
@@ -203,7 +205,7 @@
             mRemoveSurfaceCalled = false;
             int[] hideTypes = new int[1];
             mConsumer.setControl(
-                    new InsetsSourceControl(ITYPE_STATUS_BAR, statusBars(), mLeash,
+                    new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash,
                             false /* initialVisible */, new Point(), Insets.NONE),
                     new int[1], hideTypes);
             assertTrue(mRemoveSurfaceCalled);
@@ -219,7 +221,7 @@
             ViewRootInsetsControllerHost host = new ViewRootInsetsControllerHost(mViewRoot);
             InsetsController insetsController = new InsetsController(host, (controller, source) -> {
                 if (source.getType() == ime()) {
-                    return new InsetsSourceConsumer(ITYPE_IME, ime(), state,
+                    return new InsetsSourceConsumer(ID_IME, ime(), state,
                             () -> mMockTransaction, controller) {
                         @Override
                         public int requestShow(boolean fromController,
@@ -231,11 +233,11 @@
                 return new InsetsSourceConsumer(source.getId(), source.getType(),
                         controller.getState(), Transaction::new, controller);
             }, host.getHandler());
-            InsetsSource imeSource = new InsetsSource(ITYPE_IME, ime());
+            InsetsSource imeSource = new InsetsSource(ID_IME, ime());
             InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(imeSource);
 
             // Initial IME insets source control with its leash.
-            imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, ime(), mLeash,
+            imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash,
                     false /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
             reset(mMockTransaction);
 
@@ -244,7 +246,7 @@
             insetsController.controlWindowInsetsAnimation(ime(), 0L,
                     null /* interpolator */, null /* cancellationSignal */, null /* listener */);
             assertEquals(ANIMATION_TYPE_USER, insetsController.getAnimationType(ime()));
-            imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, ime(), mLeash,
+            imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash,
                     true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
             verify(mMockTransaction, never()).show(mLeash);
         });
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index e01440c..6fa8f11 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -16,15 +16,20 @@
 
 package android.view;
 
+import static android.view.WindowInsets.Type.FIRST;
+import static android.view.WindowInsets.Type.LAST;
+import static android.view.WindowInsets.Type.SIZE;
 import static android.view.WindowInsets.Type.captionBar;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.navigationBars;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -206,6 +211,21 @@
         assertEquals(Insets.of(0, 0, 0, 0), insets);
     }
 
+    @Test
+    public void testCreateId() {
+        final int numSourcePerType = 2048;
+        final int numTotalSources = SIZE * numSourcePerType;
+        final SparseArray<InsetsSource> sources = new SparseArray<>(numTotalSources);
+        final Object owner = new Object();
+        for (int index = 0; index < numSourcePerType; index++) {
+            for (int type = FIRST; type <= LAST; type = type << 1) {
+                final int id = InsetsSource.createId(owner, index, type);
+                assertNull("Must not create the same ID.", sources.get(id));
+                sources.append(id, new InsetsSource(id, type));
+            }
+        }
+        assertEquals(numTotalSources, sources.size());
+    }
 
     // Parcel and equals already tested via InsetsStateTest
 }
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 6a96f28..fde1a6d 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -18,24 +18,20 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.InsetsState.ISIDE_BOTTOM;
 import static android.view.InsetsState.ISIDE_TOP;
-import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES;
-import static android.view.InsetsState.ITYPE_CAPTION_BAR;
-import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
 import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
 import static android.view.RoundedCorner.POSITION_TOP_LEFT;
 import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
+import static android.view.WindowInsets.Type.captionBar;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowInsets.Type.systemGestures;
 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
@@ -47,8 +43,10 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
 import android.graphics.Insets;
@@ -76,33 +74,50 @@
 @RunWith(AndroidJUnit4.class)
 public class InsetsStateTest {
 
-    private InsetsState mState = new InsetsState();
-    private InsetsState mState2 = new InsetsState();
+    private static final int ID_STATUS_BAR = InsetsSource.createId(
+            null /* owner */, 0 /* index */, statusBars());
+    private static final int ID_NAVIGATION_BAR = InsetsSource.createId(
+            null /* owner */, 0 /* index */, navigationBars());
+    private static final int ID_CAPTION_BAR = InsetsSource.createId(
+            null /* owner */, 0 /* index */, captionBar());
+    private static final int ID_CLIMATE_BAR = InsetsSource.createId(
+            null /* owner */, 1 /* index */, statusBars());
+    private static final int ID_EXTRA_NAVIGATION_BAR = InsetsSource.createId(
+            null /* owner */, 1 /* index */, navigationBars());
+    private static final int ID_BOTTOM_GESTURES = InsetsSource.createId(
+            null /* owner */, 0 /* index */, systemGestures());
+
+    private final InsetsState mState = new InsetsState();
+    private final InsetsState mState2 = new InsetsState();
 
     @Test
     public void testCalculateInsets() {
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
-        mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
-        mState.getSource(ITYPE_IME).setVisible(true);
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisible(true);
+        mState.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 200, 100, 300))
+                .setVisible(true);
         SparseIntArray typeSideMap = new SparseIntArray();
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
                 false, SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
                 typeSideMap);
         assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets());
         assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all()));
-        assertEquals(ISIDE_TOP, typeSideMap.get(ITYPE_STATUS_BAR));
-        assertEquals(ISIDE_BOTTOM, typeSideMap.get(ITYPE_IME));
+        assertEquals(ISIDE_TOP, typeSideMap.get(ID_STATUS_BAR));
+        assertEquals(ISIDE_BOTTOM, typeSideMap.get(ID_IME));
         assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars()));
         assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(ime()));
     }
 
     @Test
     public void testCalculateInsets_imeAndNav() {
-        mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(0, 200, 100, 300));
-        mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(true);
-        mState.getSource(ITYPE_IME).setFrame(new Rect(0, 100, 100, 300));
-        mState.getSource(ITYPE_IME).setVisible(true);
+        mState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars())
+                .setFrame(new Rect(0, 200, 100, 300))
+                .setVisible(true);
+        mState.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 100, 100, 300))
+                .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
                 false, SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
                 null);
@@ -116,10 +131,12 @@
 
     @Test
     public void testCalculateInsets_navRightStatusTop() {
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
-        mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
-        mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(true);
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisible(true);
+        mState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars())
+                .setFrame(new Rect(80, 0, 100, 300))
+                .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
                 false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
         assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
@@ -129,14 +146,14 @@
 
     @Test
     public void testCalculateInsets_extraNavRightClimateTop() throws Exception {
-        mState.getSource(ITYPE_CLIMATE_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_CLIMATE_BAR).setVisible(true);
-        mState.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
-        mState.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setVisible(true);
+        mState.getOrCreateSource(ID_CLIMATE_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisible(true);
+        mState.getOrCreateSource(ID_EXTRA_NAVIGATION_BAR, navigationBars())
+                .setFrame(new Rect(80, 0, 100, 300))
+                .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
                 false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
-        // ITYPE_CLIMATE_BAR is a type of status bar and ITYPE_EXTRA_NAVIGATION_BAR is a type
-        // of navigation bar.
         assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
         assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.statusBars()));
         assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.navigationBars()));
@@ -144,10 +161,12 @@
 
     @Test
     public void testCalculateInsets_imeIgnoredWithoutAdjustResize() {
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
-        mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
-        mState.getSource(ITYPE_IME).setVisible(true);
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisible(true);
+        mState.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 200, 100, 300))
+                .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
                 false, SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
                 null);
@@ -158,10 +177,12 @@
 
     @Test
     public void testCalculateInsets_systemUiFlagLayoutStable() {
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
-        mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
-        mState.getSource(ITYPE_IME).setVisible(true);
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisible(false);
+        mState.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 200, 100, 300))
+                .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
                 false, SOFT_INPUT_ADJUST_NOTHING, 0, SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION,
                 WINDOWING_MODE_UNDEFINED, null);
@@ -174,8 +195,9 @@
 
     @Test
     public void testCalculateInsets_systemUiFlagLayoutStable_windowFlagFullscreen() {
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisible(false);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
                 false, SOFT_INPUT_ADJUST_NOTHING, FLAG_FULLSCREEN, SYSTEM_UI_FLAG_LAYOUT_STABLE,
                 TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
@@ -188,8 +210,9 @@
 
     @Test
     public void testCalculateInsets_flagLayoutNoLimits() {
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
                 false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS,
                 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
@@ -211,10 +234,12 @@
 
     @Test
     public void testCalculateInsets_captionStatusBarOverlap() {
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
-        mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(0, 0, 100, 300));
-        mState.getSource(ITYPE_CAPTION_BAR).setVisible(true);
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisible(true);
+        mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+                .setFrame(new Rect(0, 0, 100, 300))
+                .setVisible(true);
 
         Insets visibleInsets = mState.calculateVisibleInsets(
                 new Rect(0, 0, 100, 400), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
@@ -224,8 +249,9 @@
 
     @Test
     public void testCalculateInsets_captionBarOffset() {
-        mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(0, 0, 100, 300));
-        mState.getSource(ITYPE_CAPTION_BAR).setVisible(true);
+        mState.getOrCreateSource(ID_CAPTION_BAR, captionBar())
+                .setFrame(new Rect(0, 0, 100, 300))
+                .setVisible(true);
 
         Insets visibleInsets = mState.calculateVisibleInsets(
                 new Rect(0, 0, 150, 400), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
@@ -235,10 +261,12 @@
 
     @Test
     public void testCalculateInsets_extraNavRightStatusTop() {
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
-        mState.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
-        mState.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setVisible(true);
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisible(true);
+        mState.getOrCreateSource(ID_EXTRA_NAVIGATION_BAR, navigationBars())
+                .setFrame(new Rect(80, 0, 100, 300))
+                .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
                 false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
         assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
@@ -248,10 +276,12 @@
 
     @Test
     public void testCalculateInsets_navigationRightClimateTop() {
-        mState.getSource(ITYPE_CLIMATE_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_CLIMATE_BAR).setVisible(true);
-        mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300));
-        mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(true);
+        mState.getOrCreateSource(ID_CLIMATE_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisible(true);
+        mState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars())
+                .setFrame(new Rect(80, 0, 100, 300))
+                .setVisible(true);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false,
                 false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
         assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets());
@@ -261,11 +291,13 @@
 
     @Test
     public void testStripForDispatch() {
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
-        mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
-        mState.getSource(ITYPE_IME).setVisible(true);
-        mState.removeSource(ITYPE_IME);
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisible(true);
+        mState.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 200, 100, 300))
+                .setVisible(true);
+        mState.removeSource(ID_IME);
         WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false,
                 SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null);
         assertEquals(0, insets.getSystemWindowInsetBottom());
@@ -273,32 +305,42 @@
 
     @Test
     public void testEquals_differentRect() {
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState2.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 10, 10));
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100));
+        mState2.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 10, 10));
         assertNotEqualsAndHashCode();
     }
 
     @Test
     public void testEquals_differentSource() {
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState2.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100));
+        mState2.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 0, 100, 100));
         assertNotEqualsAndHashCode();
     }
 
     @Test
     public void testEquals_sameButDifferentInsertOrder() {
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
-        mState2.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
-        mState2.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100));
+        mState.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 0, 100, 100));
+        mState2.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 0, 100, 100));
+        mState2.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100));
         assertEqualsAndHashCode();
     }
 
     @Test
     public void testEquals_visibility() {
-        mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_IME).setVisible(true);
-        mState2.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
+        mState.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisible(true);
+        mState2.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 0, 100, 100));
         assertNotEqualsAndHashCode();
     }
 
@@ -363,19 +405,23 @@
 
     @Test
     public void testEquals_excludeInvisibleIme() {
-        mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_IME).setVisible(false);
-        mState2.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 200));
-        mState2.getSource(ITYPE_IME).setVisible(false);
+        mState.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisible(false);
+        mState2.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 0, 100, 200))
+                .setVisible(false);
         assertTrue(mState2.equals(mState, true, true /* excludeInvisibleIme */));
     }
 
     @Test
     public void testParcelUnparcel() {
-        mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_IME).setVisibleFrame(new Rect(0, 0, 50, 10));
-        mState.getSource(ITYPE_IME).setVisible(true);
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+        mState.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisibleFrame(new Rect(0, 0, 50, 10))
+                .setVisible(true);
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100));
         Parcel p = Parcel.obtain();
         mState.writeToParcel(p, 0 /* flags */);
         p.setDataPosition(0);
@@ -386,35 +432,31 @@
 
     @Test
     public void testCopy() {
-        mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_IME).setVisibleFrame(new Rect(0, 0, 50, 10));
-        mState.getSource(ITYPE_IME).setVisible(true);
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState2.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(0, 0, 100, 100));
+        mState.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisibleFrame(new Rect(0, 0, 50, 10))
+                .setVisible(true);
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100));
+        mState2.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars())
+                .setFrame(new Rect(0, 0, 100, 100));
         mState2.set(mState, true);
         assertEquals(mState, mState2);
     }
 
     @Test
-    public void testGetDefaultVisibility() {
-        assertTrue(InsetsState.getDefaultVisibility(ITYPE_STATUS_BAR));
-        assertTrue(InsetsState.getDefaultVisibility(ITYPE_NAVIGATION_BAR));
-        assertTrue(InsetsState.getDefaultVisibility(ITYPE_CLIMATE_BAR));
-        assertTrue(InsetsState.getDefaultVisibility(ITYPE_EXTRA_NAVIGATION_BAR));
-        assertTrue(InsetsState.getDefaultVisibility(ITYPE_CAPTION_BAR));
-        assertFalse(InsetsState.getDefaultVisibility(ITYPE_IME));
-    }
-
-    @Test
     public void testCalculateVisibleInsets() {
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
-        mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
-        mState.getSource(ITYPE_IME).setVisible(true);
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisible(true);
+        mState.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 200, 100, 300))
+                .setVisible(true);
 
         // Make sure bottom gestures are ignored
-        mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
-        mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+        mState.getOrCreateSource(ID_BOTTOM_GESTURES, systemGestures())
+                .setFrame(new Rect(0, 100, 100, 300))
+                .setVisible(true);
         Insets visibleInsets = mState.calculateVisibleInsets(
                 new Rect(0, 0, 100, 300), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
                 SOFT_INPUT_ADJUST_PAN, 0 /* windowFlags */);
@@ -423,14 +465,17 @@
 
     @Test
     public void testCalculateVisibleInsets_adjustNothing() {
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
-        mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
-        mState.getSource(ITYPE_IME).setVisible(true);
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisible(true);
+        mState.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 200, 100, 300))
+                .setVisible(true);
 
         // Make sure bottom gestures are ignored
-        mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
-        mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+        mState.getOrCreateSource(ID_BOTTOM_GESTURES, systemGestures())
+                .setFrame(new Rect(0, 100, 100, 300))
+                .setVisible(true);
         Insets visibleInsets = mState.calculateVisibleInsets(
                 new Rect(0, 0, 100, 300), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
                 SOFT_INPUT_ADJUST_NOTHING, 0 /* windowFlags */);
@@ -439,14 +484,17 @@
 
     @Test
     public void testCalculateVisibleInsets_layoutNoLimits() {
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
-        mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
-        mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
-        mState.getSource(ITYPE_IME).setVisible(true);
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 100, 100))
+                .setVisible(true);
+        mState.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 200, 100, 300))
+                .setVisible(true);
 
         // Make sure bottom gestures are ignored
-        mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
-        mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+        mState.getOrCreateSource(ID_BOTTOM_GESTURES, systemGestures())
+                .setFrame(new Rect(0, 100, 100, 300))
+                .setVisible(true);
         Insets visibleInsets = mState.calculateVisibleInsets(
                 new Rect(0, 0, 100, 300), TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
                 SOFT_INPUT_ADJUST_PAN, FLAG_LAYOUT_NO_LIMITS);
@@ -455,12 +503,15 @@
 
     @Test
     public void testCalculateUncontrollableInsets() {
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 200, 100));
-        mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
-        mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 200, 300));
-        mState.getSource(ITYPE_IME).setVisible(true);
-        mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(100, 0, 200, 300));
-        mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(true);
+        mState.getOrCreateSource(ID_STATUS_BAR, statusBars())
+                .setFrame(new Rect(0, 0, 200, 100))
+                .setVisible(true);
+        mState.getOrCreateSource(ID_IME, ime())
+                .setFrame(new Rect(0, 200, 200, 300))
+                .setVisible(true);
+        mState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars())
+                .setFrame(new Rect(100, 0, 200, 300))
+                .setVisible(true);
 
         mState.setDisplayFrame(new Rect(0, 0, 200, 300));
         assertEquals(0,
@@ -539,4 +590,77 @@
         assertNotEquals(mState, mState2);
         assertNotEquals(mState.hashCode(), mState2.hashCode());
     }
+
+    @Test
+    public void testTraverse() {
+        // The type doesn't matter in this test.
+        final int type = statusBars();
+
+        final InsetsState insetsState1 = new InsetsState();
+        insetsState1.getOrCreateSource(2000, type);
+        insetsState1.getOrCreateSource(1000, type);
+        insetsState1.getOrCreateSource(3000, type);
+
+        final InsetsState insetsState2 = new InsetsState();
+        insetsState2.getOrCreateSource(3000, type);
+        insetsState2.getOrCreateSource(4000, type);
+        insetsState2.getOrCreateSource(2000, type);
+        insetsState2.getOrCreateSource(5000, type);
+
+        final int[] onStartCalled = {0};
+        final int[] onIdMatchCalled = {0};
+        final int[] onIdNotFoundInState1Called = {0};
+        final int[] onIdNotFoundInState2Called = {0};
+        final int[] onFinishCalled = {0};
+
+        InsetsState.traverse(insetsState1, insetsState2, new InsetsState.OnTraverseCallbacks() {
+            @Override
+            public void onStart(InsetsState state1, InsetsState state2) {
+                assertSame("state1 must be the same as insetsState1", state1, insetsState1);
+                assertSame("state2 must be the same as insetsState2", state2, insetsState2);
+                onStartCalled[0]++;
+            }
+
+            @Override
+            public void onIdMatch(InsetsSource source1, InsetsSource source2) {
+                assertNotNull("source1 must not be null.", source1);
+                assertNotNull("source2 must not be null.", source2);
+                assertEquals("Source IDs must match.", source1.getId(), source2.getId());
+                onIdMatchCalled[0]++;
+            }
+
+            @Override
+            public void onIdNotFoundInState1(int index2, InsetsSource source2) {
+                assertNotNull("source2 must not be null.", source2);
+                assertSame(source2 + " must be placed at " + index2 + " of insetsState2",
+                        source2, insetsState2.sourceAt(index2));
+                assertNull("state1 must not have " + source2,
+                        insetsState1.peekSource(source2.getId()));
+                onIdNotFoundInState1Called[0]++;
+            }
+
+            @Override
+            public void onIdNotFoundInState2(int index1, InsetsSource source1) {
+                assertNotNull("source1 must not be null.", source1);
+                assertSame(source1 + " must be placed at " + index1 + " of insetsState1",
+                        source1, insetsState1.sourceAt(index1));
+                assertNull("state2 must not have " + source1,
+                        insetsState2.peekSource(source1.getId()));
+                onIdNotFoundInState2Called[0]++;
+            }
+
+            @Override
+            public void onFinish(InsetsState state1, InsetsState state2) {
+                assertSame("state1 must be the same as insetsState1", state1, insetsState1);
+                assertSame("state2 must be the same as insetsState2", state2, insetsState2);
+                onFinishCalled[0]++;
+            }
+        });
+
+        assertEquals(1, onStartCalled[0]);
+        assertEquals(2, onIdMatchCalled[0]); // 2000 and 3000.
+        assertEquals(2, onIdNotFoundInState1Called[0]); // 4000 and 5000.
+        assertEquals(1, onIdNotFoundInState2Called[0]); // 1000.
+        assertEquals(1, onFinishCalled[0]);
+    }
 }
diff --git a/core/tests/coretests/src/android/view/SurfaceControlViewHostInsetsTest.java b/core/tests/coretests/src/android/view/SurfaceControlViewHostInsetsTest.java
index 4731e81..4a00b00 100644
--- a/core/tests/coretests/src/android/view/SurfaceControlViewHostInsetsTest.java
+++ b/core/tests/coretests/src/android/view/SurfaceControlViewHostInsetsTest.java
@@ -16,35 +16,26 @@
 
 package android.view;
 
+import static android.view.WindowInsets.Type.statusBars;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-
 import android.app.Instrumentation;
 import android.content.Context;
 import android.graphics.Insets;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
-import android.view.InsetsState.InternalInsetsType;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.WindowInsets;
-import android.view.WindowInsets.Type;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class SurfaceControlViewHostInsetsTest {
@@ -78,9 +69,12 @@
 
     private InsetsState statusBarState(boolean visible) {
         final InsetsState insetsState = new InsetsState();
+        final int id = InsetsSource.createId(null /* owner */, 0 /* index */, statusBars());
         insetsState.setDisplayFrame(new Rect(0, 0, 1000, 1000));
-        insetsState.getSource(ITYPE_STATUS_BAR).setVisible(visible);
-        insetsState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 10));
+        insetsState.getOrCreateSource(
+                InsetsSource.createId(null /* owner */, 0 /* index */, statusBars()), statusBars())
+                        .setVisible(visible)
+                        .setFrame(new Rect(0, 0, 100, 10));
         return insetsState;
     }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index 666b472..76e0e1e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -48,7 +48,7 @@
     // TODO(b/241126279) Introduce constants to better version functionality
     @Override
     public int getVendorApiLevel() {
-        return 2;
+        return 3;
     }
 
     @NonNull
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 87c2822..20602a1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -25,6 +25,7 @@
 import android.util.ArraySet;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.window.extensions.core.util.function.Consumer;
 
 import com.android.internal.R;
@@ -185,6 +186,27 @@
         }
     }
 
+    @Override
+    public void addRearDisplayPresentationStatusListener(
+            @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {}
+
+    @Override
+    public void removeRearDisplayPresentationStatusListener(
+            @NonNull Consumer<ExtensionWindowAreaStatus> consumer) {}
+
+    @Override
+    public void startRearDisplayPresentationSession(@NonNull Activity activity,
+            @NonNull Consumer<@WindowAreaSessionState Integer> consumer) {}
+
+    @Override
+    public void endRearDisplayPresentationSession() {}
+
+    @Override
+    @Nullable
+    public ExtensionWindowAreaPresentation getRearDisplayPresentation() {
+        return null;
+    }
+
     @GuardedBy("mLock")
     private int getCurrentStatus() {
         if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 77284c41..825c670 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -17,7 +17,8 @@
 package androidx.window.extensions.embedding;
 
 import android.app.Activity;
-import android.content.res.Configuration;
+import android.os.Binder;
+import android.os.IBinder;
 import android.util.Pair;
 import android.util.Size;
 import android.window.WindowContainerTransaction;
@@ -36,6 +37,8 @@
     private final SplitRule mSplitRule;
     @NonNull
     private SplitAttributes mSplitAttributes;
+    @NonNull
+    private final IBinder mToken;
 
     SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
             @NonNull Activity primaryActivity,
@@ -46,6 +49,7 @@
         mSecondaryContainer = secondaryContainer;
         mSplitRule = splitRule;
         mSplitAttributes = splitAttributes;
+        mToken = new Binder("SplitContainer");
 
         if (shouldFinishPrimaryWithSecondary(splitRule)) {
             if (mPrimaryContainer.getRunningActivityCount() == 1
@@ -83,6 +87,11 @@
         return mSplitAttributes;
     }
 
+    @NonNull
+    IBinder getToken() {
+        return mToken;
+    }
+
     /**
      * Updates the {@link SplitAttributes} to this container.
      * It is usually used when there's a folding state change or
@@ -112,7 +121,7 @@
     @NonNull
     SplitInfo toSplitInfo() {
         return new SplitInfo(mPrimaryContainer.toActivityStack(),
-                mSecondaryContainer.toActivityStack(), mSplitAttributes);
+                mSecondaryContainer.toActivityStack(), mSplitAttributes, mToken);
     }
 
     static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 57ba6bb..ff58201 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -230,6 +230,14 @@
         return mSplitAttributesCalculator;
     }
 
+    @Override
+    @NonNull
+    public ActivityOptions setLaunchingActivityStack(@NonNull ActivityOptions options,
+            @NonNull IBinder token) {
+        options.setLaunchTaskFragmentToken(token);
+        return options;
+    }
+
     @NonNull
     @GuardedBy("mLock")
     @VisibleForTesting
@@ -2064,6 +2072,7 @@
                     transactionRecord.apply(false /* shouldApplyIndependently */);
                     // Amend the request to let the WM know that the activity should be placed in
                     // the dedicated container.
+                    // TODO(b/229680885): skip override launching TaskFragment token by split-rule
                     options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
                             launchedInTaskFragment.getTaskFragmentToken());
                     mCurrentIntent = intent;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 861cb49..38ac719 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -243,7 +243,7 @@
 
     @NonNull
     ActivityStack toActivityStack() {
-        return new ActivityStack(collectNonFinishingActivities(), isEmpty());
+        return new ActivityStack(collectNonFinishingActivities(), isEmpty(), mToken);
     }
 
     /** Adds the activity that will be reparented to this container. */
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 7eee6da..7a6f46c 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 360bfe7..e36dfc3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -68,10 +68,14 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
+import android.view.IWindowManager;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
 import android.view.WindowManager;
+import android.window.ScreenCapture;
+import android.window.ScreenCapture.ScreenCaptureListener;
+import android.window.ScreenCapture.ScreenshotSync;
 
 import androidx.annotation.MainThread;
 import androidx.annotation.Nullable;
@@ -143,6 +147,7 @@
     private final SyncTransactionQueue mSyncQueue;
     private final ShellController mShellController;
     private final ShellCommandHandler mShellCommandHandler;
+    private final IWindowManager mWmService;
 
     // Used to post to main UI thread
     private final ShellExecutor mMainExecutor;
@@ -237,7 +242,8 @@
             @ShellMainThread Handler mainHandler,
             @ShellBackgroundThread ShellExecutor bgExecutor,
             TaskViewTransitions taskViewTransitions,
-            SyncTransactionQueue syncQueue) {
+            SyncTransactionQueue syncQueue,
+            IWindowManager wmService) {
         mContext = context;
         mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
@@ -269,6 +275,7 @@
         mOneHandedOptional = oneHandedOptional;
         mDragAndDropController = dragAndDropController;
         mSyncQueue = syncQueue;
+        mWmService = wmService;
         shellInit.addInitCallback(this::onInit, this);
     }
 
@@ -1037,6 +1044,21 @@
     }
 
     /**
+     * Performs a screenshot that may exclude the bubble layer, if one is present. The screenshot
+     * can be access via the supplied {@link ScreenshotSync#get()} asynchronously.
+     *
+     * TODO(b/267324693): Implement the exclude layer functionality in screenshot.
+     */
+    public void getScreenshotExcludingBubble(int displayId,
+            Pair<ScreenCaptureListener, ScreenshotSync> screenCaptureListener) {
+        try {
+            mWmService.captureDisplay(displayId, null, screenCaptureListener.first);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to capture screenshot");
+        }
+    }
+
+    /**
      * Fills the overflow bubbles by loading them from disk.
      */
     void loadOverflowBubblesFromDisk() {
@@ -1750,6 +1772,25 @@
         }
 
         @Override
+        public boolean isAppBubbleTaskId(int taskId) {
+            Bubble appBubble = mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE);
+            return appBubble != null && appBubble.getTaskId() == taskId;
+        }
+
+        @Override
+        @Nullable
+        public ScreenshotSync getScreenshotExcludingBubble(int displayId) {
+            Pair<ScreenCaptureListener, ScreenshotSync> screenCaptureListener =
+                    ScreenCapture.createSyncCaptureListener();
+
+            mMainExecutor.execute(
+                    () -> BubbleController.this.getScreenshotExcludingBubble(displayId,
+                            screenCaptureListener));
+
+            return screenCaptureListener.second;
+        }
+
+        @Override
         public boolean handleDismissalInterception(BubbleEntry entry,
                 @Nullable List<BubbleEntry> children, IntConsumer removeCallback,
                 Executor callbackExecutor) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index df43257..1753cda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -16,6 +16,8 @@
 
 package com.android.wm.shell.bubbles;
 
+import static android.window.ScreenCapture.ScreenshotSync;
+
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
 import static java.lang.annotation.ElementType.PARAMETER;
@@ -24,11 +26,13 @@
 import android.app.NotificationChannel;
 import android.content.Intent;
 import android.content.pm.UserInfo;
+import android.hardware.HardwareBuffer;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.RankingMap;
 import android.util.Pair;
 import android.util.SparseArray;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
@@ -132,6 +136,18 @@
      */
     void showOrHideAppBubble(Intent intent);
 
+    /** @return true if the specified {@code taskId} corresponds to app bubble's taskId. */
+    boolean isAppBubbleTaskId(int taskId);
+
+    /**
+     * @return a {@link ScreenshotSync} after performing a screenshot that may exclude the bubble
+     * layer, if one is present. The underlying {@link ScreenshotHardwareBuffer} can be access via
+     * {@link ScreenshotSync#get()} asynchronously and care should be taken to
+     * {@link HardwareBuffer#close()} the associated
+     * {@link ScreenshotHardwareBuffer#getHardwareBuffer()} when no longer required.
+     */
+    ScreenshotSync getScreenshotExcludingBubble(int displayId);
+
     /**
      * @return a bubble that matches the provided shortcutId, if one exists.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index d0aef20..a775db9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -141,7 +141,7 @@
         if (pd == null) {
             return false;
         }
-        final InsetsSource imeSource = pd.mInsetsState.getSource(InsetsState.ITYPE_IME);
+        final InsetsSource imeSource = pd.mInsetsState.peekSource(InsetsSource.ID_IME);
         return imeSource != null && pd.mImeSourceControl != null && imeSource.isVisible();
     }
 
@@ -245,14 +245,17 @@
                 return;
             }
 
-            updateImeVisibility(insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME));
+            updateImeVisibility(insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME,
+                    WindowInsets.Type.ime()));
 
-            final InsetsSource newSource = insetsState.getSource(InsetsState.ITYPE_IME);
-            final Rect newFrame = newSource.getFrame();
-            final Rect oldFrame = mInsetsState.getSource(InsetsState.ITYPE_IME).getFrame();
+            final InsetsSource newSource = insetsState.peekSource(InsetsSource.ID_IME);
+            final Rect newFrame = newSource != null ? newSource.getFrame() : null;
+            final boolean newSourceVisible = newSource != null && newSource.isVisible();
+            final InsetsSource oldSource = mInsetsState.peekSource(InsetsSource.ID_IME);
+            final Rect oldFrame = oldSource != null ? oldSource.getFrame() : null;
 
             mInsetsState.set(insetsState, true /* copySources */);
-            if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) {
+            if (mImeShowing && !Objects.equals(oldFrame, newFrame) && newSourceVisible) {
                 if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation");
                 startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */);
             }
@@ -351,7 +354,7 @@
          * Sends the local visibility state back to window manager. Needed for legacy adjustForIme.
          */
         private void setVisibleDirectly(boolean visible) {
-            mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible);
+            mInsetsState.setSourceVisible(InsetsSource.ID_IME, visible);
             mRequestedVisibleTypes = visible
                     ? mRequestedVisibleTypes | WindowInsets.Type.ime()
                     : mRequestedVisibleTypes & ~WindowInsets.Type.ime();
@@ -382,7 +385,7 @@
 
         private void startAnimation(final boolean show, final boolean forceRestart,
                 @Nullable ImeTracker.Token statsToken) {
-            final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME);
+            final InsetsSource imeSource = mInsetsState.peekSource(InsetsSource.ID_IME);
             if (imeSource == null || mImeSourceControl == null) {
                 ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE);
                 return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index 8484013..b8e8363 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -372,7 +372,7 @@
 
         // Only navigation bar
         if (hasNavigationBar) {
-            final InsetsSource extraNavBar = insetsState.getSource(ITYPE_EXTRA_NAVIGATION_BAR);
+            final InsetsSource extraNavBar = insetsState.peekSource(ITYPE_EXTRA_NAVIGATION_BAR);
             final boolean hasExtraNav = extraNavBar != null && extraNavBar.isVisible();
             int position = navigationBarPosition(res, displayWidth, displayHeight, displayRotation);
             int navBarSize =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index c634198..5933ac2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -37,6 +37,7 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -216,13 +217,14 @@
     void onInsetsChanged(InsetsState insetsState, boolean animate) {
         mSplitLayout.getDividerBounds(mTempRect);
         final InsetsSource taskBarInsetsSource =
-                insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+                insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
         // Only insets the divider bar with task bar when it's expanded so that the rounded corners
         // will be drawn against task bar.
         // But there is no need to do it when IME showing because there are no rounded corners at
         // the bottom. This also avoids the problem of task bar height not changing when IME
         // floating.
-        if (!insetsState.getSourceOrDefaultVisibility(InsetsState.ITYPE_IME)
+        if (!insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME, WindowInsets.Type.ime())
+                && taskBarInsetsSource != null
                 && taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
             mTempRect.inset(taskBarInsetsSource.calculateVisibleInsets(mTempRect));
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 4879d86..d83f1eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -21,6 +21,7 @@
 import android.os.Handler;
 import android.os.UserManager;
 import android.view.Choreographer;
+import android.view.IWindowManager;
 import android.view.WindowManager;
 
 import com.android.internal.jank.InteractionJankMonitor;
@@ -170,14 +171,15 @@
             @ShellMainThread Handler mainHandler,
             @ShellBackgroundThread ShellExecutor bgExecutor,
             TaskViewTransitions taskViewTransitions,
-            SyncTransactionQueue syncQueue) {
+            SyncTransactionQueue syncQueue,
+            IWindowManager wmService) {
         return new BubbleController(context, shellInit, shellCommandHandler, shellController, data,
                 null /* synchronizer */, floatingContentCoordinator,
                 new BubbleDataRepository(context, launcherApps, mainExecutor),
                 statusBarService, windowManager, windowManagerShellWrapper, userManager,
                 launcherApps, logger, taskStackListener, organizer, positioner, displayController,
                 oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor,
-                taskViewTransitions, syncQueue);
+                taskViewTransitions, syncQueue, wmService);
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
index eab82f0..87e447b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
@@ -85,7 +85,7 @@
 
     @Override
     public void insetsChanged(InsetsState insetsState) {
-        mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+        mTaskbarInsetsSource = insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
         for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
             AnimationContext context = mAnimationContextByTaskId.valueAt(i);
             context.update(mTaskbarInsetsSource, context.mTaskInfo);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
index 6e10ebe..8a0fbe3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
@@ -126,7 +126,7 @@
 
     @Override
     public void insetsChanged(InsetsState insetsState) {
-        mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+        mTaskbarInsetsSource = insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
         updateContexts();
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index ffb1a4d..01e2f98 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -17,7 +17,7 @@
 package com.android.wm.shell.common;
 
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.Surface.ROTATION_0;
 import static android.view.WindowInsets.Type.ime;
 
@@ -138,15 +138,15 @@
     private InsetsSourceControl[] insetsSourceControl() {
         return new InsetsSourceControl[]{
                 new InsetsSourceControl(
-                        ITYPE_IME, ime(), mock(SurfaceControl.class), false, new Point(0, 0),
+                        ID_IME, ime(), mock(SurfaceControl.class), false, new Point(0, 0),
                         Insets.NONE)
         };
     }
 
     private InsetsState insetsStateWithIme(boolean visible) {
         InsetsState state = new InsetsState();
-        state.addSource(new InsetsSource(ITYPE_IME, ime()));
-        state.setSourceVisible(ITYPE_IME, visible);
+        state.addSource(new InsetsSource(ID_IME, ime()));
+        state.setSourceVisible(ID_IME, visible);
         return state;
     }
 
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 985ac3c..9e9012e 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -253,23 +253,19 @@
         public void onStop() { }
 
         /**
-         * Indicates the width and height of the captured region in pixels. Called immediately after
-         * capture begins to provide the app with accurate sizing for the stream. Also called
-         * when the region captured in this MediaProjection session is resized.
+         * Invoked immediately after capture begins or when the size of the captured region changes,
+         * providing the accurate sizing for the streamed capture.
          * <p>
          * The given width and height, in pixels, corresponds to the same width and height that
-         * would be returned from {@link android.view.WindowMetrics#getBounds()}
+         * would be returned from {@link android.view.WindowMetrics#getBounds()} of the captured
+         * region.
          * </p>
          * <p>
-         * Without the application resizing the {@link VirtualDisplay} (returned from
-         * {@code MediaProjection#createVirtualDisplay}) and output {@link Surface} (provided
-         * to {@code MediaProjection#createVirtualDisplay}), the captured stream will have
-         * letterboxing (black bars) around the recorded content to make up for the
-         * difference in aspect ratio.
-         * </p>
-         * <p>
-         * The application can prevent the letterboxing by overriding this method, and
-         * updating the size of both the {@link VirtualDisplay} and output {@link Surface}:
+         * If the recorded content has a different aspect ratio from either the
+         * {@link VirtualDisplay} or output {@link Surface}, the captured stream has letterboxing
+         * (black bars) around the recorded content. The application can avoid the letterboxing
+         * around the recorded content by updating the size of both the {@link VirtualDisplay} and
+         * output {@link Surface}:
          * </p>
          *
          * <pre>
@@ -293,27 +289,29 @@
         public void onCapturedContentResize(int width, int height) { }
 
         /**
-         * Indicates the visibility of the captured region has changed. Called immediately after
-         * capture begins with the initial visibility state, and when visibility changes. Provides
-         * the app with accurate state for presenting its own UI. The application can take advantage
-         * of this by showing or hiding the captured content, based on if the captured region is
-         * currently visible to the user.
+         * Invoked immediately after capture begins or when the visibility of the captured region
+         * changes, providing the current visibility of the captured region.
+         * <p>
+         * Applications can take advantage of this callback by showing or hiding the captured
+         * content from the output {@link Surface}, based on if the captured region is currently
+         * visible to the user.
+         * </p>
          * <p>
          * For example, if the user elected to capture a single app (from the activity shown from
-         * {@link MediaProjectionManager#createScreenCaptureIntent()}), the callback may be
-         * triggered for the following reasons:
+         * {@link MediaProjectionManager#createScreenCaptureIntent()}), the following scenarios
+         * trigger the callback:
          * <ul>
          *     <li>
-         *         The captured region may become visible ({@code isVisible} with value
-         *         {@code true}), because the captured app is at least partially visible. This may
-         *         happen if the captured app was previously covered by another app. The other app
-         *         moves to show at least some portion of the captured app.
+         *         The captured region is visible ({@code isVisible} with value {@code true}),
+         *         because the captured app is at least partially visible. This may happen if the
+         *         user moves the covering app to show at least some portion of the captured app
+         *         (e.g. the user has multiple apps visible in a multi-window mode such as split
+         *         screen).
          *     </li>
          *     <li>
-         *         The captured region may become invisible ({@code isVisible} with value
-         *         {@code false}) if it is entirely hidden. This may happen if the captured app is
-         *         entirely covered by another app, or the user navigates away from the captured
-         *         app.
+         *         The captured region is invisible ({@code isVisible} with value {@code false}) if
+         *         it is entirely hidden. This may happen if another app entirely covers the
+         *         captured app, or the user navigates away from the captured app.
          *     </li>
          * </ul>
          * </p>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index a48cd2b..7b53e26 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -24,7 +24,7 @@
 import android.content.pm.SigningInfo
 import android.credentials.CreateCredentialRequest
 import android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL
-import android.credentials.GetCredentialOption
+import android.credentials.CredentialOption
 import android.credentials.GetCredentialRequest
 import android.credentials.ui.Constants
 import android.credentials.ui.Entry
@@ -496,8 +496,8 @@
             GetCredentialRequest.Builder(
                 Bundle()
             )
-                .addGetCredentialOption(
-                    GetCredentialOption(
+                .addCredentialOption(
+                    CredentialOption(
                         TYPE_PUBLIC_KEY_CREDENTIAL,
                         Bundle(),
                         Bundle(), /*isSystemProviderRequired=*/
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
index 5cb8d3b..fdd57ff 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
@@ -61,7 +61,7 @@
         @JvmStatic
         fun createFrom(from: android.credentials.GetCredentialRequest): GetCredentialRequest {
             return GetCredentialRequest(
-                from.getCredentialOptions.map {
+                from.credentialOptions.map {
                     GetCredentialOption.createFrom(
                         it.type,
                         it.credentialRetrievalData,
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index e54e276..c702f59 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -24,6 +24,9 @@
 import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
 import com.android.settingslib.spa.gallery.dialog.AlterDialogPageProvider
 import com.android.settingslib.spa.gallery.home.HomePageProvider
+import com.android.settingslib.spa.gallery.itemList.ItemListPageProvider
+import com.android.settingslib.spa.gallery.itemList.ItemOperatePageProvider
+import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
 import com.android.settingslib.spa.gallery.page.ChartPageProvider
 import com.android.settingslib.spa.gallery.page.FooterPageProvider
@@ -50,6 +53,8 @@
     HOME("home"),
     PREFERENCE("preference"),
     ARGUMENT("argument"),
+    ITEM_LIST("itemList"),
+    ITEM_OP_PAGE("itemOp"),
 
     // Add your SPPs
 }
@@ -76,6 +81,9 @@
                 LoadingBarPageProvider,
                 ChartPageProvider,
                 AlterDialogPageProvider,
+                ItemListPageProvider,
+                ItemOperatePageProvider,
+                OperateListPageProvider,
             ),
             rootPages = listOf(
                 HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
index decc292..df1d7d1 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/button/ActionButtonsPage.kt
@@ -24,8 +24,8 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.button.ActionButton
@@ -39,13 +39,24 @@
 object ActionButtonPageProvider : SettingsPageProvider {
     override val name = "ActionButton"
 
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+
     @Composable
     override fun Page(arguments: Bundle?) {
-        ActionButtonPage()
+        RegularScaffold(title = TITLE) {
+            val actionButtons = listOf(
+                ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+                ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {},
+                ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {},
+            )
+            ActionButtons(actionButtons)
+        }
     }
 
     fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+        return SettingsEntryBuilder.createInject(owner = createSettingsPage())
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
@@ -55,22 +66,10 @@
     }
 }
 
-@Composable
-fun ActionButtonPage() {
-    RegularScaffold(title = TITLE) {
-        val actionButtons = listOf(
-            ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
-            ActionButton(text = "Uninstall", imageVector = Icons.Outlined.Delete) {},
-            ActionButton(text = "Force stop", imageVector = Icons.Outlined.WarningAmber) {},
-        )
-        ActionButtons(actionButtons)
-    }
-}
-
 @Preview(showBackground = true)
 @Composable
 private fun ActionButtonPagePreview() {
     SettingsTheme {
-        ActionButtonPage()
+        ActionButtonPageProvider.Page(null)
     }
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
index 5d26b34..0878fc0 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
@@ -18,6 +18,7 @@
 
 import android.os.Bundle
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -28,6 +29,7 @@
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
 import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
 import com.android.settingslib.spa.gallery.dialog.AlterDialogPageProvider
+import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
 import com.android.settingslib.spa.gallery.page.ArgumentPageModel
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
 import com.android.settingslib.spa.gallery.page.ChartPageProvider
@@ -50,6 +52,7 @@
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
         return listOf(
             PreferenceMainPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            OperateListPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(),
             SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
@@ -71,8 +74,10 @@
 
     @Composable
     override fun Page(arguments: Bundle?) {
-        HomeScaffold(title = getTitle(arguments)) {
-            for (entry in buildEntry(arguments)) {
+        val title = remember { getTitle(arguments) }
+        val entries = remember { buildEntry(arguments) }
+        HomeScaffold(title) {
+            for (entry in entries) {
                 if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) {
                     entry.UiLayout(ArgumentPageModel.buildArgument(intParam = 0))
                 } else {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt
new file mode 100644
index 0000000..08e6452
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemListPage.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.itemList
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.core.os.bundleOf
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.android.settingslib.spa.framework.common.EntrySearchData
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.util.getStringArg
+import com.android.settingslib.spa.framework.util.navLink
+import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+
+private const val OPERATOR_PARAM_NAME = "opParam"
+
+object ItemListPageProvider : SettingsPageProvider {
+    override val name = SettingsPageProviderEnum.ITEM_LIST.name
+    override val displayName = SettingsPageProviderEnum.ITEM_LIST.displayName
+    override val parameter = listOf(
+        navArgument(OPERATOR_PARAM_NAME) { type = NavType.StringType },
+    )
+
+    override fun getTitle(arguments: Bundle?): String {
+        val operation = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments) ?: "NULL"
+        return "Operation: $operation"
+    }
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        if (!ItemOperatePageProvider.isValidArgs(arguments)) return emptyList()
+        val operation = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments)!!
+        val owner = createSettingsPage(arguments)
+        return listOf(
+            ItemOperatePageProvider.buildInjectEntry(operation)!!.setLink(fromPage = owner).build(),
+        )
+    }
+
+    fun buildInjectEntry(opParam: String): SettingsEntryBuilder? {
+        val arguments = bundleOf(OPERATOR_PARAM_NAME to opParam)
+        if (!ItemOperatePageProvider.isValidArgs(arguments)) return null
+
+        return SettingsEntryBuilder.createInject(
+            owner = createSettingsPage(arguments),
+            displayName = "ItemList_$opParam",
+        ).setUiLayoutFn {
+            Preference(
+                object : PreferenceModel {
+                    override val title = opParam
+                    override val onClick = navigator(
+                        SettingsPageProviderEnum.ITEM_LIST.name + parameter.navLink(it)
+                    )
+                }
+            )
+        }.setSearchDataFn {
+            EntrySearchData(title = "Operation: $opParam")
+        }
+    }
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        val title = remember { getTitle(arguments) }
+        val entries = remember { buildEntry(arguments) }
+        val itemList = remember {
+            // Add logic to get item List during runtime.
+            listOf("itemFoo", "itemBar", "itemToy")
+        }
+        RegularScaffold(title) {
+            for (item in itemList) {
+                val rtArgs = ItemOperatePageProvider.genRuntimeArguments(item)
+                for (entry in entries) {
+                    entry.UiLayout(rtArgs)
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt
new file mode 100644
index 0000000..8179356
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/ItemOperatePage.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.itemList
+
+import android.os.Bundle
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.core.os.bundleOf
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.util.getStringArg
+import com.android.settingslib.spa.framework.util.navLink
+import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.SwitchPreference
+import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+
+private const val OPERATOR_PARAM_NAME = "opParam"
+private const val ITEM_NAME_PARAM_NAME = "rt_nameParam"
+private val ALLOWED_OPERATOR_LIST = listOf("opDnD", "opPiP", "opInstall", "opConnect")
+
+object ItemOperatePageProvider : SettingsPageProvider {
+    override val name = SettingsPageProviderEnum.ITEM_OP_PAGE.name
+    override val displayName = SettingsPageProviderEnum.ITEM_OP_PAGE.displayName
+    override val parameter = listOf(
+        navArgument(OPERATOR_PARAM_NAME) { type = NavType.StringType },
+        navArgument(ITEM_NAME_PARAM_NAME) { type = NavType.StringType },
+    )
+
+    override fun getTitle(arguments: Bundle?): String {
+        // Operation name is not a runtime parameter, which should always available
+        val operation = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments) ?: "opInValid"
+        // Item name is a runtime parameter, which could be missing
+        val itemName = parameter.getStringArg(ITEM_NAME_PARAM_NAME, arguments) ?: "[unset]"
+        return "$operation on $itemName"
+    }
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        if (!isValidArgs(arguments)) return emptyList()
+
+        val owner = createSettingsPage(arguments)
+        val entryList = mutableListOf<SettingsEntry>()
+        entryList.add(
+            SettingsEntryBuilder.create("ItemName", owner)
+                .setUiLayoutFn {
+                    // Item name is a runtime parameter, which needs to be read inside UiLayoutFn
+                    val itemName = parameter.getStringArg(ITEM_NAME_PARAM_NAME, it) ?: "NULL"
+                    Preference(
+                        object : PreferenceModel {
+                            override val title = "Item $itemName"
+                        }
+                    )
+                }.build()
+        )
+
+        // Operation name is not a runtime parameter, which can be read outside.
+        val opName = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments)!!
+        entryList.add(
+            SettingsEntryBuilder.create("ItemOp", owner)
+                .setUiLayoutFn {
+                    val checked = rememberSaveable { mutableStateOf(false) }
+                    SwitchPreference(remember {
+                        object : SwitchPreferenceModel {
+                            override val title = "Item operation: $opName"
+                            override val checked = checked
+                            override val onCheckedChange =
+                                { newChecked: Boolean -> checked.value = newChecked }
+                        }
+                    })
+                }.build(),
+        )
+        return entryList
+    }
+
+    fun buildInjectEntry(opParam: String): SettingsEntryBuilder? {
+        val arguments = bundleOf(OPERATOR_PARAM_NAME to opParam)
+        if (!isValidArgs(arguments)) return null
+
+        return SettingsEntryBuilder.createInject(
+            owner = createSettingsPage(arguments),
+            displayName = "ItemOp_$opParam",
+        ).setUiLayoutFn {
+            // Item name is a runtime parameter, which needs to be read inside UiLayoutFn
+            val itemName = parameter.getStringArg(ITEM_NAME_PARAM_NAME, it) ?: "NULL"
+            Preference(
+                object : PreferenceModel {
+                    override val title = "item: $itemName"
+                    override val onClick = navigator(
+                        SettingsPageProviderEnum.ITEM_OP_PAGE.name + parameter.navLink(it)
+                    )
+                }
+            )
+        }
+    }
+
+    fun isValidArgs(arguments: Bundle?): Boolean {
+        val opParam = parameter.getStringArg(OPERATOR_PARAM_NAME, arguments)
+        return (opParam != null && ALLOWED_OPERATOR_LIST.contains(opParam))
+    }
+
+    fun genRuntimeArguments(itemName: String): Bundle {
+        return bundleOf(ITEM_NAME_PARAM_NAME to itemName)
+    }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/OperateListPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/OperateListPage.kt
new file mode 100644
index 0000000..e0baf86
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/itemList/OperateListPage.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.itemList
+
+import android.os.Bundle
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+
+private const val TITLE = "Operate List Main"
+
+object OperateListPageProvider : SettingsPageProvider {
+    override val name = "OpList"
+    private val owner = createSettingsPage()
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        return listOf(
+            ItemListPageProvider.buildInjectEntry("opPiP")!!.setLink(fromPage = owner).build(),
+            ItemListPageProvider.buildInjectEntry("opInstall")!!.setLink(fromPage = owner).build(),
+            ItemListPageProvider.buildInjectEntry("opDnD")!!.setLink(fromPage = owner).build(),
+            ItemListPageProvider.buildInjectEntry("opConnect")!!.setLink(fromPage = owner).build(),
+        )
+    }
+
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner = owner)
+            .setUiLayoutFn {
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                })
+            }
+    }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index 42ac1ac..eca47b6 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -18,6 +18,7 @@
 
 import android.os.Bundle
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
@@ -101,10 +102,13 @@
 
     @Composable
     override fun Page(arguments: Bundle?) {
-        RegularScaffold(title = getTitle(arguments)) {
-            for (entry in buildEntry(arguments)) {
+        val title = remember { getTitle(arguments) }
+        val entries = remember { buildEntry(arguments) }
+        val rtArgNext = remember { ArgumentPageModel.buildNextArgument(arguments) }
+        RegularScaffold(title) {
+            for (entry in entries) {
                 if (entry.toPage != null) {
-                    entry.UiLayout(ArgumentPageModel.buildNextArgument(arguments))
+                    entry.UiLayout(rtArgNext)
                 } else {
                     entry.UiLayout()
                 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
index 5f15865..5d6aa03 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
@@ -132,7 +132,7 @@
             override val title = PAGE_TITLE
             override val summary = stateOf(summaryArray.joinToString(", "))
             override val onClick = navigator(
-                SettingsPageProviderEnum.ARGUMENT.displayName + parameter.navLink(arguments)
+                SettingsPageProviderEnum.ARGUMENT.name + parameter.navLink(arguments)
             )
         }
     }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
index 7f21a4d..eeab085 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
@@ -21,8 +21,8 @@
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.chart.BarChart
@@ -36,7 +36,6 @@
 import com.android.settingslib.spa.widget.chart.PieChartModel
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import com.github.mikephil.charting.formatter.IAxisValueFormatter
 import java.text.NumberFormat
 
@@ -47,9 +46,13 @@
 
 object ChartPageProvider : SettingsPageProvider {
     override val name = "Chart"
+    val owner = createSettingsPage()
+
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
 
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        val owner = SettingsPage.create(name)
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create("Line Chart", owner)
@@ -133,7 +136,7 @@
     }
 
     fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+        return SettingsEntryBuilder.createInject(owner)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
@@ -141,15 +144,6 @@
                 })
             }
     }
-
-    @Composable
-    override fun Page(arguments: Bundle?) {
-        RegularScaffold(title = TITLE) {
-            for (entry in buildEntry(arguments)) {
-                entry.UiLayout(arguments)
-            }
-        }
-    }
 }
 
 @Preview(showBackground = true)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
index 9f24ea9..2328fcb 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
@@ -23,8 +23,8 @@
 import com.android.settingslib.spa.framework.common.EntrySearchData
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -37,9 +37,9 @@
 
 object FooterPageProvider : SettingsPageProvider {
     override val name = "Footer"
+    val owner = createSettingsPage()
 
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        val owner = SettingsPage.create(name)
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create( "Some Preference", owner)
@@ -58,7 +58,7 @@
     }
 
     fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+        return SettingsEntryBuilder.createInject(owner)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
index 44f0343..45b7989 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
@@ -21,8 +21,8 @@
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.gallery.R
@@ -36,9 +36,9 @@
 
 object IllustrationPageProvider : SettingsPageProvider {
     override val name = "Illustration"
+    val owner = createSettingsPage()
 
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        val owner = SettingsPage.create(name)
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create( "Lottie Illustration", owner)
@@ -71,7 +71,7 @@
     }
 
      fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+        return SettingsEntryBuilder.createInject(owner)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt
index 4332a81..247990c 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/LoadingBarPage.kt
@@ -29,8 +29,8 @@
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.preference.Preference
@@ -45,7 +45,7 @@
     override val name = "LoadingBar"
 
     fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+        return SettingsEntryBuilder.createInject(owner = createSettingsPage())
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
index 20d90dd..9026a24 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
@@ -28,8 +28,8 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.preference.Preference
@@ -47,7 +47,7 @@
     override val name = "ProgressBar"
 
     fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+        return SettingsEntryBuilder.createInject(owner = createSettingsPage())
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
index c0d0abc..dc45e6d 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
@@ -23,8 +23,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.preference.Preference
@@ -39,7 +39,7 @@
     override val name = "SettingsPager"
 
     fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+        return SettingsEntryBuilder.createInject(owner = createSettingsPage())
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
index a62ec7b..74e49a6 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
@@ -29,8 +29,8 @@
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.preference.SliderPreference
@@ -42,9 +42,9 @@
 
 object SliderPageProvider : SettingsPageProvider {
     override val name = "Slider"
+    val owner = createSettingsPage()
 
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        val owner = SettingsPage.create(name)
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create("Simple Slider", owner)
@@ -104,7 +104,7 @@
     }
 
     fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+        return SettingsEntryBuilder.createInject(owner)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
index 67e35dc..d100d9d 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
@@ -24,8 +24,8 @@
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -38,9 +38,9 @@
 
 object MainSwitchPreferencePageProvider : SettingsPageProvider {
     override val name = "MainSwitchPreference"
+    val owner = createSettingsPage()
 
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        val owner = SettingsPage.create(name)
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create( "MainSwitchPreference", owner)
@@ -59,7 +59,7 @@
     }
 
     fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+        return SettingsEntryBuilder.createInject(owner)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
index 067911c..6ad4bd8 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
@@ -27,8 +27,8 @@
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -43,9 +43,9 @@
 
 object SwitchPreferencePageProvider : SettingsPageProvider {
     override val name = "SwitchPreference"
+    val owner = createSettingsPage()
 
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        val owner = SettingsPage.create(name)
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create( "SwitchPreference", owner)
@@ -82,7 +82,7 @@
     }
 
     fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+        return SettingsEntryBuilder.createInject(owner)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
index 33e5e8d..770f9a0 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
@@ -25,8 +25,8 @@
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -40,9 +40,9 @@
 
 object TwoTargetSwitchPreferencePageProvider : SettingsPageProvider {
     override val name = "TwoTargetSwitchPreference"
+    val owner = createSettingsPage()
 
     override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
-        val owner = SettingsPage.create(name)
         val entryList = mutableListOf<SettingsEntry>()
         entryList.add(
             SettingsEntryBuilder.create( "TwoTargetSwitchPreference", owner)
@@ -73,7 +73,7 @@
     }
 
     fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+        return SettingsEntryBuilder.createInject(owner)
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
index 8fdc22f..aeba6ea 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
@@ -26,8 +26,8 @@
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
-import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.widget.preference.Preference
@@ -42,7 +42,7 @@
     override val name = "Spinner"
 
     fun buildInjectEntry(): SettingsEntryBuilder {
-        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+        return SettingsEntryBuilder.createInject(owner = createSettingsPage())
             .setUiLayoutFn {
                 Preference(object : PreferenceModel {
                     override val title = TITLE
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index a8432d6..bccd8aa 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -139,9 +139,7 @@
                     ) + fadeOut(animationSpec = fadeEffect)
                 },
             ) { navBackStackEntry ->
-                val page = remember(navBackStackEntry.arguments) {
-                    spp.createSettingsPage(navBackStackEntry.arguments)
-                }
+                val page = remember { spp.createSettingsPage(navBackStackEntry.arguments) }
                 page.PageWithEvent()
             }
         }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 8bbeb62..94cfcc2 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -22,6 +22,7 @@
 import com.android.settingslib.spa.framework.util.isRuntimeParam
 import com.android.settingslib.spa.framework.util.navLink
 import com.android.settingslib.spa.framework.util.normalize
+import com.android.settingslib.spa.framework.util.normalizeArgList
 
 private const val NULL_PAGE_NAME = "NULL"
 
@@ -114,9 +115,10 @@
 fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
     return SettingsPage.create(
         name = name,
-        displayName = displayName,
+        displayName = displayName + parameter.normalizeArgList(arguments, eraseRuntimeValues = true)
+            .joinToString("") { arg -> "/$arg" },
         parameter = parameter,
-        arguments = arguments
+        arguments = arguments,
     )
 }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 42e5f7e..c564130 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -18,6 +18,7 @@
 
 import android.os.Bundle
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.navigation.NamedNavArgument
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 
@@ -52,8 +53,10 @@
     /** The [Composable] used to render this page. */
     @Composable
     fun Page(arguments: Bundle?) {
-        RegularScaffold(title = getTitle(arguments)) {
-            for (entry in buildEntry(arguments)) {
+        val title = remember { getTitle(arguments) }
+        val entries = remember { buildEntry(arguments) }
+        RegularScaffold(title) {
+            for (entry in entries) {
                 entry.UiLayout()
             }
         }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
index be303f0..f0eeb13 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
@@ -29,8 +29,16 @@
 }
 
 fun List<NamedNavArgument>.navLink(arguments: Bundle? = null): String {
+    return normalizeArgList(arguments).joinToString("") { arg -> "/$arg" }
+}
+
+fun List<NamedNavArgument>.normalizeArgList(
+    arguments: Bundle? = null,
+    eraseRuntimeValues: Boolean = false
+): List<String> {
     val argsArray = mutableListOf<String>()
     for (navArg in this) {
+        if (eraseRuntimeValues && navArg.isRuntimeParam()) continue
         if (arguments == null || !arguments.containsKey(navArg.name)) {
             argsArray.add(UNSET_PARAM_VALUE)
             continue
@@ -44,7 +52,7 @@
             }
         }
     }
-    return argsArray.joinToString("") { arg -> "/$arg" }
+    return argsArray
 }
 
 fun List<NamedNavArgument>.normalize(
@@ -55,7 +63,7 @@
     val normArgs = Bundle()
     for (navArg in this) {
         // Erase value of runtime parameters.
-        if (navArg.isRuntimeParam() && eraseRuntimeValues) {
+        if (eraseRuntimeValues && navArg.isRuntimeParam()) {
             normArgs.putString(navArg.name, null)
             continue
         }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
index 1f5de2d..7fa1e26 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -75,7 +75,7 @@
             )
         )
         assertThat(page.sppName).isEqualTo("SppWithParam")
-        assertThat(page.displayName).isEqualTo("SppWithParam")
+        assertThat(page.displayName).isEqualTo("SppWithParam/myStr/10")
         assertThat(page.buildRoute()).isEqualTo("SppWithParam/myStr/10")
         assertThat(page.isCreateBy("SppWithParam")).isTrue()
         assertThat(page.hasRuntimeParam()).isFalse()
@@ -100,7 +100,7 @@
             )
         )
         assertThat(page.sppName).isEqualTo("SppWithRtParam")
-        assertThat(page.displayName).isEqualTo("SppWithRtParam")
+        assertThat(page.displayName).isEqualTo("SppWithRtParam/myStr/10")
         assertThat(page.buildRoute()).isEqualTo("SppWithRtParam/myStr/10/rtStr")
         assertThat(page.isCreateBy("SppWithRtParam")).isTrue()
         assertThat(page.hasRuntimeParam()).isTrue()
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 8508878..c155433 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1315,6 +1315,13 @@
     <string name="media_transfer_this_device_name" product="tablet">This tablet</string>
     <!-- Name of the phone device with an active remote session. [CHAR LIMIT=30] -->
     <string name="media_transfer_this_phone">This phone</string>
+    <!-- Sub status indicates device need premium account. [CHAR LIMIT=NONE] -->
+    <string name="media_output_status_require_premium">Upgrade account to switch</string>
+    <!-- Sub status indicates device not support download content. [CHAR LIMIT=NONE] -->
+    <string name="media_output_status_not_support_downloads">Can\’t play downloads here</string>
+    <!-- Sub status indicates device need to wait after ad. [CHAR LIMIT=NONE] -->
+    <string name="media_output_status_try_after_ad">Try again after the ad</string>
+
 
     <!-- Warning message to tell user is have problem during profile connect, it need to turn off device and back on. [CHAR_LIMIT=NONE] -->
     <string name="profile_connect_timeout_subtext">Problem connecting. Turn device off &amp; back on</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index 21e7d81..48d449d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -101,6 +101,9 @@
     @VisibleForTesting
     static ApplicationsState sInstance;
 
+    // Whether the app icon cache mechanism is enabled or not.
+    private static boolean sAppIconCacheEnabled = false;
+
     public static ApplicationsState getInstance(Application app) {
         return getInstance(app, AppGlobals.getPackageManager());
     }
@@ -115,6 +118,11 @@
         }
     }
 
+    /** Set whether the app icon cache mechanism is enabled or not. */
+    public static void setAppIconCacheEnabled(boolean enabled) {
+        sAppIconCacheEnabled = enabled;
+    }
+
     final Context mContext;
     final PackageManager mPm;
     final IPackageManager mIpm;
@@ -776,7 +784,8 @@
     }
 
     private static boolean isAppIconCacheEnabled(Context context) {
-        return SETTING_PKG.equals(context.getPackageName());
+        return SETTING_PKG.equals(context.getPackageName())
+                || sAppIconCacheEnabled;
     }
 
     void rebuildActiveSessions() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 2bdbb16..d222b98 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -34,10 +34,12 @@
 
 import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
 
+import android.annotation.Nullable;
 import android.annotation.TargetApi;
 import android.app.Notification;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.content.ComponentName;
 import android.content.Context;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
@@ -191,6 +193,14 @@
                 && Api34Impl.preferRouteListingOrdering(mRouterManager, mPackageName);
     }
 
+    @Nullable
+    ComponentName getLinkedItemComponentName() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+            return null;
+        }
+        return Api34Impl.getLinkedItemComponentName(mRouterManager, mPackageName);
+    }
+
     /**
      * Remove a {@code device} from current media.
      *
@@ -682,6 +692,16 @@
         }
 
         @DoNotInline
+        @Nullable
+        static ComponentName getLinkedItemComponentName(
+                MediaRouter2Manager mediaRouter2Manager, String packageName) {
+            RouteListingPreference routeListingPreference =
+                    mediaRouter2Manager.getRouteListingPreference(packageName);
+            return routeListingPreference == null ? null
+                    : routeListingPreference.getLinkedItemComponentName();
+        }
+
+        @DoNotInline
         static void onRouteListingPreferenceUpdated(
                 String packageName,
                 RouteListingPreference routeListingPreference,
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index aec1767..24acf8a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -20,6 +20,7 @@
 import android.app.Notification;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
@@ -217,6 +218,16 @@
     }
 
     /**
+     * Returns required component name for system to take the user back to the app by launching an
+     * intent with the returned {@link ComponentName}, using action {@link #ACTION_TRANSFER_MEDIA},
+     * with the extra {@link #EXTRA_ROUTE_ID}.
+     */
+    @Nullable
+    public ComponentName getLinkedItemComponentName() {
+        return mInfoMediaManager.getLinkedItemComponentName();
+    }
+
+    /**
      * Start scan connected MediaDevice
      */
     public void startScan() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 17ef283..156993d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -30,8 +30,14 @@
 import static android.media.MediaRoute2Info.TYPE_USB_HEADSET;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
 import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+import static android.media.RouteListingPreference.Item.FLAG_ONGOING_SESSION;
 import static android.media.RouteListingPreference.Item.FLAG_SUGGESTED_ROUTE;
+import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_NONE;
+import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
+import static android.media.RouteListingPreference.Item.SUBTEXT_CUSTOM;
+import static android.media.RouteListingPreference.Item.SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED;
 import static android.media.RouteListingPreference.Item.SUBTEXT_NONE;
+import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
 
 import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
 
@@ -51,6 +57,8 @@
 import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.settingslib.R;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -194,29 +202,59 @@
     public abstract String getId();
 
     /**
-     * Get disabled reason of device
+     * Get selection behavior of device
      *
-     * @return disabled reason of device
+     * @return selection behavior of device
      */
     @RouteListingPreference.Item.SubText
-    public int getDisableReason() {
+    public int getSelectionBehavior() {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null
-                ? mItem.getSubText()
-                : -1;
+                ? mItem.getSelectionBehavior() : SELECTION_BEHAVIOR_NONE;
     }
 
     /**
-     * Checks if device is has disabled reason
+     * Checks if device is has subtext
      *
-     * @return true if device has disabled reason
+     * @return true if device has subtext
      */
-    public boolean hasDisabledReason() {
+    public boolean hasSubtext() {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
                 && mItem != null
                 && mItem.getSubText() != SUBTEXT_NONE;
     }
 
     /**
+     * Get subtext of device
+     *
+     * @return subtext of device
+     */
+    @RouteListingPreference.Item.SubText
+    public int getSubtext() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null
+                ? mItem.getSubText() : SUBTEXT_NONE;
+    }
+
+    /**
+     * Returns subtext string for current route.
+     *
+     * @return subtext string for this route
+     */
+    public String getSubtextString() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null
+                ? Api34Impl.composeSubtext(mItem, mContext) : null;
+    }
+
+    /**
+     * Checks if device has ongoing shared session, which allow user to join
+     *
+     * @return true if device has ongoing session
+     */
+    public boolean hasOngoingSession() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+                && Api34Impl.hasOngoingSession(mItem);
+    }
+
+    /**
      * Checks if device is suggested device from application
      *
      * @return true if device is suggested device
@@ -513,7 +551,27 @@
     private static class Api34Impl {
         @DoNotInline
         static boolean isSuggestedDevice(RouteListingPreference.Item item) {
-            return item != null && item.getFlags() == FLAG_SUGGESTED_ROUTE;
+            return item != null && (item.getFlags() & FLAG_SUGGESTED_ROUTE) != 0;
+        }
+
+        @DoNotInline
+        static boolean hasOngoingSession(RouteListingPreference.Item item) {
+            return item != null && (item.getFlags() & FLAG_ONGOING_SESSION) != 0;
+        }
+
+        @DoNotInline
+        static String composeSubtext(RouteListingPreference.Item item, Context context) {
+            switch (item.getSubText()) {
+                case SUBTEXT_SUBSCRIPTION_REQUIRED:
+                    return context.getString(R.string.media_output_status_require_premium);
+                case SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED:
+                    return context.getString(R.string.media_output_status_not_support_downloads);
+                case SUBTEXT_AD_ROUTING_DISALLOWED:
+                    return context.getString(R.string.media_output_status_try_after_ad);
+                case SUBTEXT_CUSTOM:
+                    return (String) item.getCustomSubtextMessage();
+            }
+            return "";
         }
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 2c8aa26..31038cd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -35,6 +35,7 @@
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothDevice;
+import android.content.ComponentName;
 import android.content.Context;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
@@ -88,6 +89,8 @@
     private MediaManager.MediaDeviceCallback mCallback;
     @Mock
     private MediaSessionManager mMediaSessionManager;
+    @Mock
+    private ComponentName mComponentName;
 
     private InfoMediaManager mInfoMediaManager;
     private Context mContext;
@@ -372,6 +375,24 @@
         assertThat(mInfoMediaManager.preferRouteListingOrdering()).isFalse();
     }
 
+    @Test
+    public void getInAppOnlyItemRoutingReceiver_oldSdkVersion_returnsNull() {
+        assertThat(mInfoMediaManager.getLinkedItemComponentName()).isNull();
+    }
+
+    @Test
+    public void getInAppOnlyItemRoutingReceiver_newSdkVersionWithReceiverExist_returns() {
+        ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
+                Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
+        when(mRouterManager.getRouteListingPreference(any())).thenReturn(
+                new RouteListingPreference.Builder().setItems(
+                        ImmutableList.of()).setUseSystemOrdering(
+                        false).setLinkedItemComponentName(mComponentName).build());
+        mInfoMediaManager.mRouterManager = mRouterManager;
+
+        assertThat(mInfoMediaManager.getLinkedItemComponentName()).isEqualTo(mComponentName);
+    }
+
     private List<MediaRoute2Info> getRoutesListWithDuplicatedIds() {
         final List<MediaRoute2Info> routes = new ArrayList<>();
         final MediaRoute2Info info = mock(MediaRoute2Info.class);
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index c0818a8..7cd8d70 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -99,5 +99,7 @@
         Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_MS,
         Settings.Global.Wearable.WEAR_ACTIVITY_AUTO_RESUME_TIMEOUT_SET_BY_USER,
         Settings.Global.Wearable.DYNAMIC_COLOR_THEME_ENABLED,
+        Settings.Global.HDR_CONVERSION_MODE,
+        Settings.Global.HDR_FORCE_CONVERSION_TYPE,
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 673a3ab..3ffbabd 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -16,6 +16,9 @@
 
 package android.provider.settings.validators;
 
+import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_FORCE;
+import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_PASSTHROUGH;
+import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_SYSTEM;
 import static android.media.AudioFormat.SURROUND_SOUND_ENCODING;
 import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
@@ -26,6 +29,11 @@
 import static android.provider.settings.validators.SettingsValidators.PACKAGE_NAME_VALIDATOR;
 import static android.provider.settings.validators.SettingsValidators.PERCENTAGE_INTEGER_VALIDATOR;
 import static android.view.Display.HdrCapabilities.HDR_TYPES;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_HDR10;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_HDR10_PLUS;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_HLG;
+import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
 
 import android.os.BatteryManager;
 import android.provider.Settings.Global;
@@ -346,6 +354,20 @@
         VALIDATORS.put(Global.USER_PREFERRED_REFRESH_RATE, NON_NEGATIVE_FLOAT_VALIDATOR);
         VALIDATORS.put(Global.USER_PREFERRED_RESOLUTION_HEIGHT, ANY_INTEGER_VALIDATOR);
         VALIDATORS.put(Global.USER_PREFERRED_RESOLUTION_WIDTH, ANY_INTEGER_VALIDATOR);
+        VALIDATORS.put(Global.HDR_CONVERSION_MODE,  new DiscreteValueValidator(
+                new String[] {
+                        String.valueOf(HDR_CONVERSION_PASSTHROUGH),
+                        String.valueOf(HDR_CONVERSION_SYSTEM),
+                        String.valueOf(HDR_CONVERSION_FORCE)
+                }));
+        VALIDATORS.put(Global.HDR_FORCE_CONVERSION_TYPE, new DiscreteValueValidator(
+                new String[] {
+                        String.valueOf(HDR_TYPE_INVALID),
+                        String.valueOf(HDR_TYPE_DOLBY_VISION),
+                        String.valueOf(HDR_TYPE_HDR10),
+                        String.valueOf(HDR_TYPE_HLG),
+                        String.valueOf(HDR_TYPE_HDR10_PLUS)
+                }));
         VALIDATORS.put(Global.RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO_ENABLED,
                        new DiscreteValueValidator(new String[]{"0", "1"}));
         VALIDATORS.put(Global.Wearable.WET_MODE_ON, BOOLEAN_VALIDATOR);
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 8e98d2d..64e1bc2 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -418,6 +418,34 @@
                  android:permission="com.android.systemui.permission.SELF"
                  android:exported="false" />
 
+        <activity android:name=".screenshot.AppClipsTrampolineActivity"
+            android:theme="@style/AppClipsTrampolineActivity"
+            android:label="@string/screenshot_preview_description"
+            android:permission="android.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE"
+            android:exported="true">
+            <intent-filter android:priority="1">
+                <action android:name="android.intent.action.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".screenshot.AppClipsActivity"
+            android:theme="@style/AppClipsActivity"
+            android:process=":appclips.screenshot"
+            android:label="@string/screenshot_preview_description"
+            android:permission="com.android.systemui.permission.SELF"
+            android:excludeFromRecents="true"
+            android:exported="false"
+            android:noHistory="true" />
+
+        <service android:name=".screenshot.appclips.AppClipsScreenshotHelperService"
+            android:permission="com.android.systemui.permission.SELF"
+            android:exported="false" />
+
+        <service android:name=".screenshot.appclips.AppClipsService"
+            android:permission="android.permission.LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE"
+            android:exported="true" />
+
         <service android:name=".screenrecord.RecordingService"
                  android:foregroundServiceType="systemExempted"/>
 
diff --git a/packages/SystemUI/res/drawable/media_output_status_help.xml b/packages/SystemUI/res/drawable/media_output_status_help.xml
new file mode 100644
index 0000000..f93c07ec
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_status_help.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@color/media_dialog_item_main_content"
+        android:pathData="M11.95,18Q12.475,18 12.838,17.637Q13.2,17.275 13.2,16.75Q13.2,16.225 12.838,15.863Q12.475,15.5 11.95,15.5Q11.425,15.5 11.062,15.863Q10.7,16.225 10.7,16.75Q10.7,17.275 11.062,17.637Q11.425,18 11.95,18ZM11.05,14.15H12.9Q12.9,13.325 13.088,12.85Q13.275,12.375 14.15,11.55Q14.8,10.9 15.175,10.312Q15.55,9.725 15.55,8.9Q15.55,7.5 14.525,6.75Q13.5,6 12.1,6Q10.675,6 9.788,6.75Q8.9,7.5 8.55,8.55L10.2,9.2Q10.325,8.75 10.763,8.225Q11.2,7.7 12.1,7.7Q12.9,7.7 13.3,8.137Q13.7,8.575 13.7,9.1Q13.7,9.6 13.4,10.037Q13.1,10.475 12.65,10.85Q11.55,11.825 11.3,12.325Q11.05,12.825 11.05,14.15ZM12,22Q9.925,22 8.1,21.212Q6.275,20.425 4.925,19.075Q3.575,17.725 2.788,15.9Q2,14.075 2,12Q2,9.925 2.788,8.1Q3.575,6.275 4.925,4.925Q6.275,3.575 8.1,2.787Q9.925,2 12,2Q14.075,2 15.9,2.787Q17.725,3.575 19.075,4.925Q20.425,6.275 21.212,8.1Q22,9.925 22,12Q22,14.075 21.212,15.9Q20.425,17.725 19.075,19.075Q17.725,20.425 15.9,21.212Q14.075,22 12,22Z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml
new file mode 100644
index 0000000..5155b77
--- /dev/null
+++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:background="@null"
+    android:id="@+id/root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <Button
+        android:id="@+id/save"
+        style="@android:style/Widget.DeviceDefault.Button.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:text="@string/app_clips_save_add_to_note"
+        android:layout_marginStart="8dp"
+        android:background="@drawable/overlay_button_background"
+        android:textColor="?android:textColorSecondary"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/preview" />
+
+    <Button
+        android:id="@+id/cancel"
+        style="@android:style/Widget.DeviceDefault.Button.Colored"
+        android:layout_width="wrap_content"
+        android:layout_height="48dp"
+        android:text="@android:string/cancel"
+        android:layout_marginStart="6dp"
+        android:background="@drawable/overlay_button_background"
+        android:textColor="?android:textColorSecondary"
+        app:layout_constraintStart_toEndOf="@id/save"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toTopOf="@id/preview" />
+
+    <ImageView
+        android:id="@+id/preview"
+        android:layout_width="0px"
+        android:layout_height="0px"
+        android:paddingHorizontal="48dp"
+        android:paddingTop="8dp"
+        android:paddingBottom="42dp"
+        android:contentDescription="@string/screenshot_preview_description"
+        app:layout_constrainedHeight="true"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintTop_toBottomOf="@id/save"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        tools:background="?android:colorBackground"
+        tools:minHeight="100dp"
+        tools:minWidth="100dp" />
+
+    <com.android.systemui.screenshot.CropView
+        android:id="@+id/crop_view"
+        android:layout_width="0px"
+        android:layout_height="0px"
+        android:paddingTop="8dp"
+        android:paddingBottom="42dp"
+        app:layout_constrainedHeight="true"
+        app:layout_constrainedWidth="true"
+        app:layout_constraintTop_toTopOf="@id/preview"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:handleThickness="@dimen/screenshot_crop_handle_thickness"
+        app:handleColor="?android:attr/colorAccent"
+        app:scrimColor="?android:colorBackgroundFloating"
+        app:scrimAlpha="128"
+        app:containerBackgroundColor="?android:colorBackgroundFloating"
+        tools:background="?android:colorBackground"
+        tools:minHeight="100dp"
+        tools:minWidth="100dp" />
+
+    <com.android.systemui.screenshot.MagnifierView
+        android:id="@+id/magnifier"
+        android:visibility="invisible"
+        android:layout_width="200dp"
+        android:layout_height="200dp"
+        android:elevation="2dp"
+        app:layout_constraintTop_toTopOf="@id/preview"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:handleThickness="@dimen/screenshot_crop_handle_thickness"
+        app:handleColor="?android:attr/colorAccent"
+        app:scrimColor="?android:colorBackgroundFloating"
+        app:scrimAlpha="128"
+        app:borderThickness="4dp"
+        app:borderColor="#fff" />
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 034f145..050b1e1 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -445,6 +445,13 @@
          screenshot has been saved to work profile. If blank, a default icon will be shown. -->
     <string name="config_sceenshotWorkProfileFilesApp" translatable="false"></string>
 
+    <!-- The component name of the screenshot editing activity that provides the App Clips flow.
+         The App Clips flow includes taking a screenshot, showing user screenshot cropping activity
+         and finally letting user send the screenshot to the calling notes app. This activity
+         should not send the screenshot to the calling activity without user consent. -->
+    <string name="config_screenshotAppClipsActivityComponent" translatable="false"
+            >com.android.systemui/com.android.systemui.screenshot.AppClipsActivity</string>
+
     <!-- Remote copy default activity.  Must handle REMOTE_COPY_ACTION intents.
      This name is in the ComponentName flattened format (package/class)  -->
     <string name="config_remoteCopyPackage" translatable="false"></string>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 943844f..b535e60 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -247,6 +247,8 @@
     <string name="screenshot_detected_template"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> detected this screenshot.</string>
     <!-- A notice shown to the user to indicate that multiple apps have detected the screenshot that the user has just taken. [CHAR LIMIT=75] -->
     <string name="screenshot_detected_multiple_template"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> and other open apps detected this screenshot.</string>
+    <!-- Add to note button used in App Clips flow to return the saved screenshot image to notes app. [CHAR LIMIT=NONE] -->
+    <string name="app_clips_save_add_to_note">Add to note</string>
 
     <!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
     <string name="screenrecord_name">Screen Recorder</string>
@@ -2538,8 +2540,7 @@
     <string name="media_output_group_title_speakers_and_displays">Speakers &amp; Displays</string>
     <!-- Title for Suggested Devices group. [CHAR LIMIT=NONE] -->
     <string name="media_output_group_title_suggested_device">Suggested Devices</string>
-    <!-- Sub status indicates device need premium account. [CHAR LIMIT=NONE] -->
-    <string name="media_output_status_require_premium">Requires premium account</string>
+
 
     <!-- Media Output Broadcast Dialog -->
     <!-- Title for Broadcast First Notify Dialog [CHAR LIMIT=60] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index dd87e91..4998d68 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -758,6 +758,18 @@
     </style>
 
     <!-- Screenshots -->
+    <style name="AppClipsTrampolineActivity">
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowIsFloating">true</item>
+        <item name="android:backgroundDimEnabled">true</item>
+    </style>
+
+    <style name="AppClipsActivity" parent="LongScreenshotActivity">
+        <item name="android:windowBackground">@android:color/transparent</item>
+        <item name="android:windowIsTranslucent">true</item>
+    </style>
+
     <style name="LongScreenshotActivity" parent="@android:style/Theme.DeviceDefault.DayNight">
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowLightStatusBar">true</item>
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index 4eb444e..a5beb4e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -23,6 +23,8 @@
 import com.android.systemui.keyguard.WorkLockActivity;
 import com.android.systemui.people.PeopleSpaceActivity;
 import com.android.systemui.people.widget.LaunchConversationActivity;
+import com.android.systemui.screenshot.AppClipsActivity;
+import com.android.systemui.screenshot.AppClipsTrampolineActivity;
 import com.android.systemui.screenshot.LongScreenshotActivity;
 import com.android.systemui.sensorprivacy.SensorUseStartedActivity;
 import com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity;
@@ -119,6 +121,18 @@
     @ClassKey(LongScreenshotActivity.class)
     public abstract Activity bindLongScreenshotActivity(LongScreenshotActivity activity);
 
+    /** Inject into AppClipsTrampolineActivity. */
+    @Binds
+    @IntoMap
+    @ClassKey(AppClipsTrampolineActivity.class)
+    public abstract Activity bindAppClipsTrampolineActivity(AppClipsTrampolineActivity activity);
+
+    /** Inject into AppClipsActivity. */
+    @Binds
+    @IntoMap
+    @ClassKey(AppClipsActivity.class)
+    public abstract Activity bindAppClipsActivity(AppClipsActivity activity);
+
     /** Inject into LaunchConversationActivity. */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 94101df..e1aed65 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -516,6 +516,9 @@
     // TODO(b/266955521): Tracking bug
     @JvmField val SCREENSHOT_DETECTION = unreleasedFlag(1303, "screenshot_detection")
 
+    // TODO(b/251205791): Tracking Bug
+    @JvmField val SCREENSHOT_APP_CLIPS = releasedFlag(1304, "screenshot_app_clips")
+
     // 1400 - columbus
     // TODO(b/254512756): Tracking Bug
     val QUICK_TAP_IN_PCC = releasedFlag(1400, "quick_tap_in_pcc")
@@ -549,7 +552,7 @@
     @JvmField val DUAL_SHADE = releasedFlag(1801, "dual_shade")
 
     // 1900
-    @JvmField val NOTE_TASKS = unreleasedFlag(1900, "keycode_flag")
+    @JvmField val NOTE_TASKS = releasedFlag(1900, "keycode_flag")
 
     // 2000 - device controls
     @Keep @JvmField val USE_APP_PANELS = releasedFlag(2000, "use_app_panels", teamfood = true)
@@ -571,10 +574,14 @@
     @JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag(2201, "udfps_ellipse_detection")
 
     // 2300 - stylus
-    @JvmField val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used")
-    @JvmField val ENABLE_STYLUS_CHARGING_UI = unreleasedFlag(2301, "enable_stylus_charging_ui")
     @JvmField
-    val ENABLE_USI_BATTERY_NOTIFICATIONS = unreleasedFlag(2302, "enable_usi_battery_notifications")
+    val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used", teamfood = true)
+    @JvmField
+    val ENABLE_STYLUS_CHARGING_UI =
+        unreleasedFlag(2301, "enable_stylus_charging_ui", teamfood = true)
+    @JvmField
+    val ENABLE_USI_BATTERY_NOTIFICATIONS =
+        unreleasedFlag(2302, "enable_usi_battery_notifications", teamfood = true)
     @JvmField val ENABLE_STYLUS_EDUCATION = unreleasedFlag(2303, "enable_stylus_education")
 
     // 2400 - performance tools and debugging info
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index e57b169..9f5d372 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,6 +16,11 @@
 
 package com.android.systemui.media.dialog;
 
+import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_GO_TO_APP;
+import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_NONE;
+import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_TRANSFER;
+import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
+import static android.media.RouteListingPreference.Item.SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED;
 import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
 
 import android.content.Context;
@@ -23,7 +28,6 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
-import android.media.RouteListingPreference;
 import android.os.Build;
 import android.util.Log;
 import android.view.View;
@@ -194,16 +198,29 @@
                     updateFullItemClickListener(v -> onItemClick(v, device));
                     setSingleLineLayout(getItemTitle(device));
                 } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
-                        && mController.isSubStatusSupported() && device.hasDisabledReason()) {
-                    //update to subtext with device status
+                        && mController.isSubStatusSupported()
+                        && mController.isAdvancedLayoutSupported() && device.hasSubtext()) {
+                    boolean isActiveWithOngoingSession =
+                            device.hasOngoingSession() && currentlyConnected;
+                    if (isActiveWithOngoingSession) {
+                        //Selected device which has ongoing session, disable seekbar since we
+                        //only allow volume control on Host
+                        mSeekBar.setVolume(0);
+                        disableSeekBar();
+                        mCurrentActivePosition = position;
+                    }
                     setUpDeviceIcon(device);
-                    mSubTitleText.setText(
-                            Api34Impl.composeDisabledReason(device.getDisableReason(), mContext));
-                    updateConnectionFailedStatusIcon();
-                    updateFullItemClickListener(null);
-                    setTwoLineLayout(device, false /* bFocused */, false /* showSeekBar */,
+                    mSubTitleText.setText(device.getSubtextString());
+                    Drawable deviceStatusIcon =
+                            Api34Impl.getDeviceStatusIconBasedOnSelectionBehavior(device, mContext);
+                    if (deviceStatusIcon != null) {
+                        updateDeviceStatusIcon(deviceStatusIcon);
+                    }
+                    updateClickActionBasedOnSelectionBehavior(device);
+                    setTwoLineLayout(device, isActiveWithOngoingSession /* bFocused */,
+                            isActiveWithOngoingSession /* showSeekBar */,
                             false /* showProgressBar */, true /* showSubtitle */,
-                            true /* showStatus */);
+                            deviceStatusIcon != null /* showStatus */);
                 } else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
                     setUpDeviceIcon(device);
                     updateConnectionFailedStatusIcon();
@@ -220,6 +237,7 @@
                             false /* showEndTouchArea */);
                 } else if (mController.getSelectedMediaDevice().size() > 1
                         && isDeviceIncluded(mController.getSelectedMediaDevice(), device)) {
+                    // selected device in group
                     boolean isDeviceDeselectable = isDeviceIncluded(
                             mController.getDeselectableMediaDevice(), device);
                     if (!mController.isAdvancedLayoutSupported()) {
@@ -235,6 +253,7 @@
                     initSeekbar(device, isCurrentSeekbarInvisible);
                 } else if (!mController.hasAdjustVolumeUserRestriction()
                         && currentlyConnected) {
+                    // single selected device
                     if (isMutingExpectedDeviceExist
                             && !mController.isCurrentConnectedDeviceRemote()) {
                         // mark as disconnected and set special click listener
@@ -266,6 +285,7 @@
                         initSeekbar(device, isCurrentSeekbarInvisible);
                     }
                 } else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
+                    //groupable device
                     setUpDeviceIcon(device);
                     updateGroupableCheckBox(false, true, device);
                     if (mController.isAdvancedLayoutSupported()) {
@@ -280,7 +300,12 @@
                 } else {
                     setUpDeviceIcon(device);
                     setSingleLineLayout(getItemTitle(device));
-                    updateFullItemClickListener(v -> onItemClick(v, device));
+                    if (mController.isAdvancedLayoutSupported()
+                            && mController.isSubStatusSupported()) {
+                        updateClickActionBasedOnSelectionBehavior(device);
+                    } else {
+                        updateFullItemClickListener(v -> onItemClick(v, device));
+                    }
                 }
             }
         }
@@ -292,12 +317,23 @@
                     ColorStateList(states, colors));
         }
 
+        private void updateClickActionBasedOnSelectionBehavior(MediaDevice device) {
+            View.OnClickListener clickListener = Api34Impl.getClickListenerBasedOnSelectionBehavior(
+                    device, mController, v -> onItemClick(v, device));
+            updateFullItemClickListener(clickListener);
+        }
+
         private void updateConnectionFailedStatusIcon() {
             mStatusIcon.setImageDrawable(
                     mContext.getDrawable(R.drawable.media_output_status_failed));
             mStatusIcon.setColorFilter(mController.getColorItemContent());
         }
 
+        private void updateDeviceStatusIcon(Drawable drawable) {
+            mStatusIcon.setImageDrawable(drawable);
+            mStatusIcon.setColorFilter(mController.getColorItemContent());
+        }
+
         private void updateProgressBarColor() {
             mProgressBar.getIndeterminateDrawable().setColorFilter(
                     new PorterDuffColorFilter(
@@ -411,13 +447,30 @@
     @RequiresApi(34)
     private static class Api34Impl {
         @DoNotInline
-        static String composeDisabledReason(
-                @RouteListingPreference.Item.SubText int reason, Context context) {
-            switch(reason) {
-                case SUBTEXT_SUBSCRIPTION_REQUIRED:
-                    return context.getString(R.string.media_output_status_require_premium);
+        static View.OnClickListener getClickListenerBasedOnSelectionBehavior(MediaDevice device,
+                MediaOutputController controller, View.OnClickListener defaultTransferListener) {
+            switch (device.getSelectionBehavior()) {
+                case SELECTION_BEHAVIOR_NONE:
+                    return null;
+                case SELECTION_BEHAVIOR_TRANSFER:
+                    return defaultTransferListener;
+                case SELECTION_BEHAVIOR_GO_TO_APP:
+                    return v -> controller.tryToLaunchInAppRoutingIntent(device.getId(), v);
             }
-            return "";
+            return null;
+        }
+
+        @DoNotInline
+        static Drawable getDeviceStatusIconBasedOnSelectionBehavior(MediaDevice device,
+                Context context) {
+            switch (device.getSubtext()) {
+                case SUBTEXT_AD_ROUTING_DISALLOWED:
+                case SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED:
+                    return context.getDrawable(R.drawable.media_output_status_failed);
+                case SUBTEXT_SUBSCRIPTION_REQUIRED:
+                    return context.getDrawable(R.drawable.media_output_status_help);
+            }
+            return null;
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index dc75538..b5e829e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -278,12 +278,29 @@
             mStatusIcon.setVisibility(showStatus ? View.VISIBLE : View.GONE);
             mSeekBar.setAlpha(1);
             mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE);
-            final Drawable backgroundDrawable = mContext.getDrawable(
-                            R.drawable.media_output_item_background)
-                    .mutate();
-            backgroundDrawable.setColorFilter(new PorterDuffColorFilter(
-                    mController.getColorItemBackground(),
-                    PorterDuff.Mode.SRC_IN));
+            final Drawable backgroundDrawable;
+            if (mController.isAdvancedLayoutSupported() && mController.isSubStatusSupported()) {
+                backgroundDrawable = mContext.getDrawable(
+                        showSeekBar ? R.drawable.media_output_item_background_active
+                                : R.drawable.media_output_item_background).mutate();
+                backgroundDrawable.setColorFilter(new PorterDuffColorFilter(
+                        showSeekBar ? mController.getColorConnectedItemBackground()
+                                : mController.getColorItemBackground(), PorterDuff.Mode.SRC_IN));
+                mIconAreaLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
+                        showSeekBar ? mController.getColorConnectedItemBackground()
+                                        : mController.getColorItemBackground(),
+                        PorterDuff.Mode.SRC_IN));
+                ViewGroup.MarginLayoutParams params =
+                        (ViewGroup.MarginLayoutParams) mItemLayout.getLayoutParams();
+                params.rightMargin = mController.getItemMarginEndDefault();
+            } else {
+                backgroundDrawable = mContext.getDrawable(
+                                R.drawable.media_output_item_background)
+                        .mutate();
+                backgroundDrawable.setColorFilter(new PorterDuffColorFilter(
+                        mController.getColorItemBackground(),
+                        PorterDuff.Mode.SRC_IN));
+            }
             mItemLayout.setBackground(backgroundDrawable);
             mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
             mSubTitleText.setVisibility(showSubtitle ? View.VISIBLE : View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 4803371..35819e3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -263,10 +263,10 @@
             mMediaOutputController.releaseSession();
             dismiss();
         });
-        mAppButton.setOnClickListener(v -> mMediaOutputController.tryToLaunchMediaApplication());
+        mAppButton.setOnClickListener(mMediaOutputController::tryToLaunchMediaApplication);
         if (mMediaOutputController.isAdvancedLayoutSupported()) {
             mMediaMetadataSectionLayout.setOnClickListener(
-                    v -> mMediaOutputController.tryToLaunchMediaApplication());
+                    mMediaOutputController::tryToLaunchMediaApplication);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 5f5c686..d234dff 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.media.dialog;
 
+import static android.media.RouteListingPreference.ACTION_TRANSFER_MEDIA;
+import static android.media.RouteListingPreference.EXTRA_ROUTE_ID;
 import static android.provider.Settings.ACTION_BLUETOOTH_SETTINGS;
 
 import android.annotation.CallbackExecutor;
@@ -24,6 +26,7 @@
 import android.app.Notification;
 import android.app.WallpaperColors;
 import android.bluetooth.BluetoothLeBroadcast;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -382,12 +385,29 @@
         return mContext.getPackageManager().getLaunchIntentForPackage(mPackageName);
     }
 
-    void tryToLaunchMediaApplication() {
+    void tryToLaunchInAppRoutingIntent(String routeId, View view) {
+        ComponentName componentName = mLocalMediaManager.getLinkedItemComponentName();
+        if (componentName != null) {
+            ActivityLaunchAnimator.Controller controller =
+                    mDialogLaunchAnimator.createActivityLaunchController(view);
+            Intent launchIntent = new Intent(ACTION_TRANSFER_MEDIA);
+            launchIntent.setComponent(componentName);
+            launchIntent.putExtra(EXTRA_ROUTE_ID, routeId);
+            launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            mCallback.dismissDialog();
+            mContext.startActivity(launchIntent);
+            mActivityStarter.startActivity(launchIntent, true, controller);
+        }
+    }
+
+    void tryToLaunchMediaApplication(View view) {
+        ActivityLaunchAnimator.Controller controller =
+                mDialogLaunchAnimator.createActivityLaunchController(view);
         Intent launchIntent = getAppLaunchIntent();
         if (launchIntent != null) {
             launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             mCallback.dismissDialog();
-            mContext.startActivity(launchIntent);
+            mActivityStarter.startActivity(launchIntent, true, controller);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 464b6e7..048d40c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -48,6 +48,7 @@
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_FOOTER_DOT
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS
@@ -158,6 +159,7 @@
         private const val DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT = false
         private const val DEFAULT_TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS = true
         private const val DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS = true
+        private const val DEFAULT_TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP = true
     }
 
     override var newChangesSinceDialogWasDismissed = false
@@ -173,6 +175,9 @@
 
     private var showUserVisibleJobs = DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS
 
+    private var informJobSchedulerOfPendingAppStop =
+        DEFAULT_TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP
+
     override val includesUserVisibleJobs: Boolean
         get() = showUserVisibleJobs
 
@@ -233,6 +238,11 @@
                 NAMESPACE_SYSTEMUI,
                 TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, DEFAULT_TASK_MANAGER_SHOW_USER_VISIBLE_JOBS)
 
+            informJobSchedulerOfPendingAppStop = deviceConfigProxy.getBoolean(
+                NAMESPACE_SYSTEMUI,
+                TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP,
+                DEFAULT_TASK_MANAGER_INFORM_JOB_SCHEDULER_OF_PENDING_APP_STOP)
+
             try {
                 activityManager.registerForegroundServiceObserver(foregroundServiceObserver)
                 // Clumping FGS and user-visible jobs here and showing a single entry and button
@@ -262,10 +272,13 @@
                     showStopBtnForUserAllowlistedApps)
                 var wasShowingUserVisibleJobs = showUserVisibleJobs
                 showUserVisibleJobs = it.getBoolean(
-                        TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, showUserVisibleJobs)
+                    TASK_MANAGER_SHOW_USER_VISIBLE_JOBS, showUserVisibleJobs)
                 if (showUserVisibleJobs != wasShowingUserVisibleJobs) {
                     onShowUserVisibleJobsFlagChanged()
                 }
+                informJobSchedulerOfPendingAppStop = it.getBoolean(
+                    TASK_MANAGER_SHOW_STOP_BUTTON_FOR_USER_ALLOWLISTED_APPS,
+                    informJobSchedulerOfPendingAppStop)
             }
 
             _isAvailable.value = deviceConfigProxy.getBoolean(
@@ -475,14 +488,11 @@
     private fun stopPackage(userId: Int, packageName: String, timeStarted: Long) {
         logEvent(stopped = true, packageName, userId, timeStarted)
         val userPackageKey = UserPackage(userId, packageName)
-        if (showUserVisibleJobs &&
-                runningTaskIdentifiers[userPackageKey]?.hasRunningJobs() == true) {
+        if (showUserVisibleJobs || informJobSchedulerOfPendingAppStop) {
             // TODO(255768978): allow fine-grained job control
-            jobScheduler.stopUserVisibleJobsForUser(packageName, userId)
+            jobScheduler.notePendingUserRequestedAppStop(packageName, userId, "task manager")
         }
-        if (runningTaskIdentifiers[userPackageKey]?.hasFgs() == true) {
-            activityManager.stopAppForUser(packageName, userId)
-        }
+        activityManager.stopAppForUser(packageName, userId)
     }
 
     private fun onShowUserVisibleJobsFlagChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index 5bc209a..7316f46 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -203,8 +203,7 @@
         // For now, restrict to debug users.
         return Build.isDebuggable()
                 && mDreamSupported
-                // TODO(b/257333623): Allow the Dock User to be non-SystemUser user in HSUM.
-                && (!mDreamOnlyEnabledForDockUser || mUserTracker.getUserHandle().isSystem());
+                && (!mDreamOnlyEnabledForDockUser || mUserTracker.getUserInfo().isMain());
     }
 
     @VisibleForTesting
@@ -224,7 +223,8 @@
 
     private ComponentName getActiveDream() {
         try {
-            final ComponentName[] dreams = mDreamManager.getDreamComponents();
+            final ComponentName[] dreams = mDreamManager.getDreamComponentsForUser(
+                                                mUserTracker.getUserId());
             return dreams != null && dreams.length > 0 ? dreams[0] : null;
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to get active dream", e);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
new file mode 100644
index 0000000..f8d86a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static com.android.systemui.screenshot.AppClipsTrampolineActivity.ACTION_FINISH_FROM_TRAMPOLINE;
+import static com.android.systemui.screenshot.AppClipsTrampolineActivity.EXTRA_RESULT_RECEIVER;
+import static com.android.systemui.screenshot.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
+import static com.android.systemui.screenshot.AppClipsTrampolineActivity.PERMISSION_SELF;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+
+import androidx.activity.ComponentActivity;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.R;
+
+import javax.inject.Inject;
+
+/**
+ * An {@link Activity} to take a screenshot for the App Clips flow and presenting a screenshot
+ * editing tool.
+ *
+ * <p>An App Clips flow includes:
+ * <ul>
+ *     <li>Checking if calling activity meets the prerequisites. This is done by
+ *     {@link AppClipsTrampolineActivity}.
+ *     <li>Performing the screenshot.
+ *     <li>Showing a screenshot editing tool.
+ *     <li>Returning the screenshot to the {@link AppClipsTrampolineActivity} so that it can return
+ *     the screenshot to the calling activity after explicit user consent.
+ * </ul>
+ *
+ * <p>This {@link Activity} runs in its own separate process to isolate memory intensive image
+ * editing from SysUI process.
+ *
+ * TODO(b/267309532): Polish UI and animations.
+ */
+public final class AppClipsActivity extends ComponentActivity {
+
+    private final AppClipsViewModel.Factory mViewModelFactory;
+    private final BroadcastReceiver mBroadcastReceiver;
+    private final IntentFilter mIntentFilter;
+
+    private View mLayout;
+    private View mRoot;
+    private ImageView mPreview;
+    private CropView mCropView;
+    private MagnifierView mMagnifierView;
+    private Button mSave;
+    private Button mCancel;
+    private AppClipsViewModel mViewModel;
+
+    private ResultReceiver mResultReceiver;
+
+    @Inject
+    public AppClipsActivity(AppClipsViewModel.Factory viewModelFactory) {
+        mViewModelFactory = viewModelFactory;
+
+        mBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                // Trampoline activity was dismissed so finish this activity.
+                if (ACTION_FINISH_FROM_TRAMPOLINE.equals(intent.getAction())) {
+                    if (!isFinishing()) {
+                        // Nullify the ResultReceiver so that result cannot be sent as trampoline
+                        // activity is already finishing.
+                        mResultReceiver = null;
+                        finish();
+                    }
+                }
+            }
+        };
+
+        mIntentFilter = new IntentFilter(ACTION_FINISH_FROM_TRAMPOLINE);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        overridePendingTransition(0, 0);
+        super.onCreate(savedInstanceState);
+
+        // Register the broadcast receiver that informs when the trampoline activity is dismissed.
+        registerReceiver(mBroadcastReceiver, mIntentFilter, PERMISSION_SELF, null,
+                RECEIVER_NOT_EXPORTED);
+
+        Intent intent = getIntent();
+        mResultReceiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER, ResultReceiver.class);
+        if (mResultReceiver == null) {
+            setErrorThenFinish(Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED);
+            return;
+        }
+
+        // Inflate layout but don't add it yet as it should be added after the screenshot is ready
+        // for preview.
+        mLayout = getLayoutInflater().inflate(R.layout.app_clips_screenshot, null);
+        mRoot = mLayout.findViewById(R.id.root);
+
+        mSave = mLayout.findViewById(R.id.save);
+        mCancel = mLayout.findViewById(R.id.cancel);
+        mSave.setOnClickListener(this::onClick);
+        mCancel.setOnClickListener(this::onClick);
+
+        mMagnifierView = mLayout.findViewById(R.id.magnifier);
+        mCropView = mLayout.findViewById(R.id.crop_view);
+        mCropView.setCropInteractionListener(mMagnifierView);
+
+        mPreview = mLayout.findViewById(R.id.preview);
+        mPreview.addOnLayoutChangeListener(
+                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+                        updateImageDimensions());
+
+        mViewModel = new ViewModelProvider(this, mViewModelFactory).get(AppClipsViewModel.class);
+        mViewModel.getScreenshot().observe(this, this::setScreenshot);
+        mViewModel.getResultLiveData().observe(this, this::setResultThenFinish);
+        mViewModel.getErrorLiveData().observe(this, this::setErrorThenFinish);
+
+        if (savedInstanceState == null) {
+            mViewModel.performScreenshot();
+        }
+    }
+
+    @Override
+    public void finish() {
+        super.finish();
+        overridePendingTransition(0, 0);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        unregisterReceiver(mBroadcastReceiver);
+
+        // If neither error nor result was set, it implies that the activity is finishing due to
+        // some other reason such as user dismissing this activity using back gesture. Inform error.
+        if (isFinishing() && mViewModel.getErrorLiveData().getValue() == null
+                && mViewModel.getResultLiveData().getValue() == null) {
+            // Set error but don't finish as the activity is already finishing.
+            setError(Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED);
+        }
+    }
+
+    private void setScreenshot(Bitmap screenshot) {
+        // Set background, status and navigation bar colors as the activity is no longer
+        // translucent.
+        int colorBackgroundFloating = Utils.getColorAttr(this,
+                android.R.attr.colorBackgroundFloating).getDefaultColor();
+        mRoot.setBackgroundColor(colorBackgroundFloating);
+
+        BitmapDrawable drawable = new BitmapDrawable(getResources(), screenshot);
+        mPreview.setImageDrawable(drawable);
+        mPreview.setAlpha(1f);
+
+        mMagnifierView.setDrawable(drawable, screenshot.getWidth(), screenshot.getHeight());
+
+        // Screenshot is now available so set content view.
+        setContentView(mLayout);
+    }
+
+    private void onClick(View view) {
+        mSave.setEnabled(false);
+        mCancel.setEnabled(false);
+
+        int id = view.getId();
+        if (id == R.id.save) {
+            saveScreenshotThenFinish();
+        } else {
+            setErrorThenFinish(Intent.CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED);
+        }
+    }
+
+    private void saveScreenshotThenFinish() {
+        Drawable drawable = mPreview.getDrawable();
+        if (drawable == null) {
+            setErrorThenFinish(Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED);
+            return;
+        }
+
+        Rect bounds = mCropView.getCropBoundaries(drawable.getIntrinsicWidth(),
+                drawable.getIntrinsicHeight());
+
+        if (bounds.isEmpty()) {
+            setErrorThenFinish(Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED);
+            return;
+        }
+
+        updateImageDimensions();
+        mViewModel.saveScreenshotThenFinish(drawable, bounds);
+    }
+
+    private void setResultThenFinish(Uri uri) {
+        if (mResultReceiver == null) {
+            return;
+        }
+
+        Bundle data = new Bundle();
+        data.putInt(Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE,
+                Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
+        data.putParcelable(EXTRA_SCREENSHOT_URI, uri);
+        try {
+            mResultReceiver.send(Activity.RESULT_OK, data);
+        } catch (Exception e) {
+            // Do nothing.
+        }
+
+        // Nullify the ResultReceiver before finishing to avoid resending the result.
+        mResultReceiver = null;
+        finish();
+    }
+
+    private void setErrorThenFinish(int errorCode) {
+        setError(errorCode);
+        finish();
+    }
+
+    private void setError(int errorCode) {
+        if (mResultReceiver == null) {
+            return;
+        }
+
+        Bundle data = new Bundle();
+        data.putInt(Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, errorCode);
+        try {
+            mResultReceiver.send(RESULT_OK, data);
+        } catch (Exception e) {
+            // Do nothing.
+        }
+
+        // Nullify the ResultReceiver to avoid resending the result.
+        mResultReceiver = null;
+    }
+
+    private void updateImageDimensions() {
+        Drawable drawable = mPreview.getDrawable();
+        if (drawable == null) {
+            return;
+        }
+
+        Rect bounds = drawable.getBounds();
+        float imageRatio = bounds.width() / (float) bounds.height();
+        int previewWidth = mPreview.getWidth() - mPreview.getPaddingLeft()
+                - mPreview.getPaddingRight();
+        int previewHeight = mPreview.getHeight() - mPreview.getPaddingTop()
+                - mPreview.getPaddingBottom();
+        float viewRatio = previewWidth / (float) previewHeight;
+
+        if (imageRatio > viewRatio) {
+            // Image is full width and height is constrained, compute extra padding to inform
+            // CropView.
+            int imageHeight = (int) (previewHeight * viewRatio / imageRatio);
+            int extraPadding = (previewHeight - imageHeight) / 2;
+            mCropView.setExtraPadding(extraPadding, extraPadding);
+            mCropView.setImageWidth(previewWidth);
+        } else {
+            // Image is full height.
+            mCropView.setExtraPadding(mPreview.getPaddingTop(), mPreview.getPaddingBottom());
+            mCropView.setImageWidth((int) (previewHeight * imageRatio));
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
new file mode 100644
index 0000000..65fb4c9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsCrossProcessHelper.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.appclips;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.view.Display;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Application;
+
+import javax.inject.Inject;
+
+/** An intermediary singleton object to help communicating with the cross process service. */
+@SysUISingleton
+public class AppClipsCrossProcessHelper {
+
+    private final ServiceConnector<IAppClipsScreenshotHelperService> mProxyConnector;
+
+    @Inject
+    public AppClipsCrossProcessHelper(@Application Context context) {
+        mProxyConnector = new ServiceConnector.Impl<IAppClipsScreenshotHelperService>(context,
+                new Intent(context, AppClipsScreenshotHelperService.class),
+                Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
+                        | Context.BIND_NOT_VISIBLE, context.getUserId(),
+                IAppClipsScreenshotHelperService.Stub::asInterface);
+    }
+
+    /**
+     * Returns a {@link Bitmap} captured in the SysUI process, {@code null} in case of an error.
+     *
+     * <p>Note: The SysUI process captures a {@link ScreenshotHardwareBufferInternal} which is ok to
+     * pass around but not a {@link Bitmap}.
+     */
+    @Nullable
+    public Bitmap takeScreenshot() {
+        try {
+            AndroidFuture<ScreenshotHardwareBufferInternal> future =
+                    mProxyConnector.postForResult(
+                            service -> service.takeScreenshot(Display.DEFAULT_DISPLAY));
+            return future.get().createBitmapThenCloseBuffer();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java
new file mode 100644
index 0000000..6f8c365
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperService.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.appclips;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
+import android.window.ScreenCapture.ScreenshotSync;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.screenshot.AppClipsActivity;
+import com.android.wm.shell.bubbles.Bubbles;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+/**
+ * A helper service that runs in SysUI process and helps {@link AppClipsActivity} which runs in its
+ * own separate process take a screenshot.
+ */
+public class AppClipsScreenshotHelperService extends Service {
+
+    private final Optional<Bubbles> mOptionalBubbles;
+
+    @Inject
+    public AppClipsScreenshotHelperService(Optional<Bubbles> optionalBubbles) {
+        mOptionalBubbles = optionalBubbles;
+    }
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new IAppClipsScreenshotHelperService.Stub() {
+            @Override
+            @Nullable
+            public ScreenshotHardwareBufferInternal takeScreenshot(int displayId) {
+                if (mOptionalBubbles.isEmpty()) {
+                    return null;
+                }
+
+                ScreenshotSync screenshotSync =
+                        mOptionalBubbles.get().getScreenshotExcludingBubble(displayId);
+                ScreenshotHardwareBuffer screenshotHardwareBuffer = screenshotSync.get();
+                if (screenshotHardwareBuffer == null) {
+                    return null;
+                }
+
+                return new ScreenshotHardwareBufferInternal(screenshotHardwareBuffer);
+            }
+        };
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
new file mode 100644
index 0000000..d0b7ad3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.appclips;
+
+import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
+
+import android.app.Activity;
+import android.app.Service;
+import android.app.StatusBarManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.IBinder;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.statusbar.IAppClipsService;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.wm.shell.bubbles.Bubbles;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+/**
+ * A service that communicates with {@link StatusBarManager} to support the
+ * {@link StatusBarManager#canLaunchCaptureContentActivityForNote(Activity)} API.
+ */
+public class AppClipsService extends Service {
+
+    @Application private final Context mContext;
+    private final FeatureFlags mFeatureFlags;
+    private final Optional<Bubbles> mOptionalBubbles;
+    private final DevicePolicyManager mDevicePolicyManager;
+    private final boolean mAreTaskAndTimeIndependentPrerequisitesMet;
+
+    @Inject
+    public AppClipsService(@Application Context context, FeatureFlags featureFlags,
+            Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager) {
+        mContext = context;
+        mFeatureFlags = featureFlags;
+        mOptionalBubbles = optionalBubbles;
+        mDevicePolicyManager = devicePolicyManager;
+
+        mAreTaskAndTimeIndependentPrerequisitesMet = checkIndependentVariables();
+    }
+
+    private boolean checkIndependentVariables() {
+        if (!mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)) {
+            return false;
+        }
+
+        if (mOptionalBubbles.isEmpty()) {
+            return false;
+        }
+
+        return isComponentValid();
+    }
+
+    private boolean isComponentValid() {
+        ComponentName componentName;
+        try {
+            componentName = ComponentName.unflattenFromString(
+                    mContext.getString(R.string.config_screenshotAppClipsActivityComponent));
+        } catch (Resources.NotFoundException e) {
+            return false;
+        }
+
+        return componentName != null
+                && !componentName.getPackageName().isEmpty()
+                && !componentName.getClassName().isEmpty();
+    }
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new IAppClipsService.Stub() {
+            @Override
+            public boolean canLaunchCaptureContentActivityForNote(int taskId) {
+                if (!mAreTaskAndTimeIndependentPrerequisitesMet) {
+                    return false;
+                }
+
+                if (!mOptionalBubbles.get().isAppBubbleTaskId(taskId)) {
+                    return false;
+                }
+
+                return !mDevicePolicyManager.getScreenCaptureDisabled(null);
+            }
+        };
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
new file mode 100644
index 0000000..4759cc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
+import static android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE;
+import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
+
+import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.notetask.NoteTaskController;
+import com.android.wm.shell.bubbles.Bubbles;
+
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+/**
+ * A trampoline activity that is responsible for:
+ * <ul>
+ *     <li>Performing precondition checks before starting the actual screenshot activity.
+ *     <li>Communicating with the screenshot activity and the calling activity.
+ * </ul>
+ *
+ * <p>As this activity is started in a bubble app, the windowing for this activity is restricted
+ * to the parent bubble app. The screenshot editing activity, see {@link AppClipsActivity}, is
+ * started in a regular activity window using {@link Intent#FLAG_ACTIVITY_NEW_TASK}. However,
+ * {@link Activity#startActivityForResult(Intent, int)} is not compatible with
+ * {@link Intent#FLAG_ACTIVITY_NEW_TASK}. So, this activity acts as a trampoline activity to
+ * abstract the complexity of communication with the screenshot editing activity for a simpler
+ * developer experience.
+ *
+ * TODO(b/267309532): Polish UI and animations.
+ */
+public class AppClipsTrampolineActivity extends Activity {
+
+    private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName();
+    public static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
+    public static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI";
+    public static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE";
+    static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
+
+    private final DevicePolicyManager mDevicePolicyManager;
+    private final FeatureFlags mFeatureFlags;
+    private final Optional<Bubbles> mOptionalBubbles;
+    private final NoteTaskController mNoteTaskController;
+    private final ResultReceiver mResultReceiver;
+
+    private Intent mKillAppClipsBroadcastIntent;
+
+    @Inject
+    public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags,
+            Optional<Bubbles> optionalBubbles, NoteTaskController noteTaskController,
+            @Main Handler mainHandler) {
+        mDevicePolicyManager = devicePolicyManager;
+        mFeatureFlags = flags;
+        mOptionalBubbles = optionalBubbles;
+        mNoteTaskController = noteTaskController;
+
+        mResultReceiver = createResultReceiver(mainHandler);
+    }
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (savedInstanceState != null) {
+            return;
+        }
+
+        if (!mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)) {
+            finish();
+            return;
+        }
+
+        if (mOptionalBubbles.isEmpty()) {
+            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+            return;
+        }
+
+        if (!mOptionalBubbles.get().isAppBubbleTaskId(getTaskId())) {
+            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED);
+            return;
+        }
+
+        if (mDevicePolicyManager.getScreenCaptureDisabled(null)) {
+            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN);
+            return;
+        }
+
+        ComponentName componentName;
+        try {
+            componentName = ComponentName.unflattenFromString(
+                    getString(R.string.config_screenshotAppClipsActivityComponent));
+        } catch (Resources.NotFoundException e) {
+            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+            return;
+        }
+
+        if (componentName == null || componentName.getPackageName().isEmpty()
+                || componentName.getClassName().isEmpty()) {
+            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+            return;
+        }
+
+        Intent intent = new Intent().setComponent(componentName).addFlags(
+                Intent.FLAG_ACTIVITY_NEW_TASK).putExtra(EXTRA_RESULT_RECEIVER, mResultReceiver);
+        try {
+            // Start the App Clips activity.
+            startActivity(intent);
+
+            // Set up the broadcast intent that will inform the above App Clips activity to finish
+            // when this trampoline activity is finished.
+            mKillAppClipsBroadcastIntent =
+                    new Intent(ACTION_FINISH_FROM_TRAMPOLINE)
+                            .setComponent(componentName)
+                            .setPackage(componentName.getPackageName());
+        } catch (ActivityNotFoundException e) {
+            setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        if (isFinishing() && mKillAppClipsBroadcastIntent != null) {
+            sendBroadcast(mKillAppClipsBroadcastIntent, PERMISSION_SELF);
+        }
+    }
+
+    private void setErrorResultAndFinish(int errorCode) {
+        setResult(RESULT_OK,
+                new Intent().putExtra(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, errorCode));
+        finish();
+    }
+
+    private class AppClipsResultReceiver extends ResultReceiver {
+
+        AppClipsResultReceiver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            if (isFinishing()) {
+                // It's too late, trampoline activity is finishing or already finished.
+                // Return early.
+                return;
+            }
+
+            // Package the response that should be sent to the calling activity.
+            Intent convertedData = new Intent();
+            int statusCode = CAPTURE_CONTENT_FOR_NOTE_FAILED;
+            if (resultData != null) {
+                statusCode = resultData.getInt(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE,
+                        CAPTURE_CONTENT_FOR_NOTE_FAILED);
+            }
+            convertedData.putExtra(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, statusCode);
+
+            if (statusCode == CAPTURE_CONTENT_FOR_NOTE_SUCCESS) {
+                Uri uri = resultData.getParcelable(EXTRA_SCREENSHOT_URI, Uri.class);
+                convertedData.setData(uri).addFlags(FLAG_GRANT_READ_URI_PERMISSION);
+            }
+
+            // Broadcast no longer required, setting it to null.
+            mKillAppClipsBroadcastIntent = null;
+
+            // Expand the note bubble before returning the result. As App Clips API is only
+            // available when in a bubble, isInMultiWindowMode is always false below.
+            mNoteTaskController.showNoteTask(false);
+            setResult(RESULT_OK, convertedData);
+            finish();
+        }
+    }
+
+    /**
+     * @return a {@link ResultReceiver} by initializing an {@link AppClipsResultReceiver} and
+     * converting it into a generic {@link ResultReceiver} to pass across a different but trusted
+     * process.
+     */
+    private ResultReceiver createResultReceiver(@Main Handler handler) {
+        AppClipsResultReceiver appClipsResultReceiver = new AppClipsResultReceiver(handler);
+        Parcel parcel = Parcel.obtain();
+        appClipsResultReceiver.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        ResultReceiver resultReceiver  = ResultReceiver.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+        return resultReceiver;
+    }
+
+    /** This is a test only API for mocking response from {@link AppClipsActivity}. */
+    @VisibleForTesting
+    public ResultReceiver getResultReceiverForTest() {
+        return mResultReceiver;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
new file mode 100644
index 0000000..5a7b5f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.HardwareRenderer;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.RenderNode;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Process;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.screenshot.appclips.AppClipsCrossProcessHelper;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.time.ZonedDateTime;
+import java.util.UUID;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/** A {@link ViewModel} to help with the App Clips screenshot flow. */
+final class AppClipsViewModel extends ViewModel {
+
+    private final AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
+    private final ImageExporter mImageExporter;
+    @Main
+    private final Executor mMainExecutor;
+    @Background
+    private final Executor mBgExecutor;
+
+    private final MutableLiveData<Bitmap> mScreenshotLiveData;
+    private final MutableLiveData<Uri> mResultLiveData;
+    private final MutableLiveData<Integer> mErrorLiveData;
+
+    AppClipsViewModel(AppClipsCrossProcessHelper appClipsCrossProcessHelper,
+            ImageExporter imageExporter, @Main Executor mainExecutor,
+            @Background Executor bgExecutor) {
+        mAppClipsCrossProcessHelper = appClipsCrossProcessHelper;
+        mImageExporter = imageExporter;
+        mMainExecutor = mainExecutor;
+        mBgExecutor = bgExecutor;
+
+        mScreenshotLiveData = new MutableLiveData<>();
+        mResultLiveData = new MutableLiveData<>();
+        mErrorLiveData = new MutableLiveData<>();
+    }
+
+    /** Grabs a screenshot and updates the {@link Bitmap} set in screenshot {@link LiveData}. */
+    void performScreenshot() {
+        mBgExecutor.execute(() -> {
+            Bitmap screenshot = mAppClipsCrossProcessHelper.takeScreenshot();
+            mMainExecutor.execute(() -> {
+                if (screenshot == null) {
+                    mErrorLiveData.setValue(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+                } else {
+                    mScreenshotLiveData.setValue(screenshot);
+                }
+            });
+        });
+    }
+
+    /** Returns a {@link LiveData} that holds the captured screenshot. */
+    LiveData<Bitmap> getScreenshot() {
+        return mScreenshotLiveData;
+    }
+
+    /** Returns a {@link LiveData} that holds the {@link Uri} where screenshot is saved. */
+    LiveData<Uri> getResultLiveData() {
+        return mResultLiveData;
+    }
+
+    /**
+     * Returns a {@link LiveData} that holds the error codes for
+     * {@link Intent#EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE}.
+     */
+    LiveData<Integer> getErrorLiveData() {
+        return mErrorLiveData;
+    }
+
+    /**
+     * Saves the provided {@link Drawable} to storage then informs the result {@link Uri} to
+     * {@link LiveData}.
+     */
+    void saveScreenshotThenFinish(Drawable screenshotDrawable, Rect bounds) {
+        mBgExecutor.execute(() -> {
+            // Render the screenshot bitmap in background.
+            Bitmap screenshotBitmap = renderBitmap(screenshotDrawable, bounds);
+
+            // Export and save the screenshot in background.
+            // TODO(b/267310185): Save to work profile UserHandle.
+            ListenableFuture<ImageExporter.Result> exportFuture = mImageExporter.export(
+                    mBgExecutor, UUID.randomUUID(), screenshotBitmap, ZonedDateTime.now(),
+                    Process.myUserHandle());
+
+            // Get the result and update state on main thread.
+            exportFuture.addListener(() -> {
+                try {
+                    ImageExporter.Result result = exportFuture.get();
+                    if (result.uri == null) {
+                        mErrorLiveData.setValue(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+                        return;
+                    }
+
+                    mResultLiveData.setValue(result.uri);
+                } catch (CancellationException | InterruptedException | ExecutionException e) {
+                    mErrorLiveData.setValue(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+                }
+            }, mMainExecutor);
+        });
+    }
+
+    private static Bitmap renderBitmap(Drawable drawable, Rect bounds) {
+        final RenderNode output = new RenderNode("Screenshot save");
+        output.setPosition(0, 0, bounds.width(), bounds.height());
+        RecordingCanvas canvas = output.beginRecording();
+        canvas.translate(-bounds.left, -bounds.top);
+        canvas.clipRect(bounds);
+        drawable.draw(canvas);
+        output.endRecording();
+        return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height());
+    }
+
+    /** Helper factory to help with injecting {@link AppClipsViewModel}. */
+    static final class Factory implements ViewModelProvider.Factory {
+
+        private final AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
+        private final ImageExporter mImageExporter;
+        @Main
+        private final Executor mMainExecutor;
+        @Background
+        private final Executor mBgExecutor;
+
+        @Inject
+        Factory(AppClipsCrossProcessHelper appClipsCrossProcessHelper,  ImageExporter imageExporter,
+                @Main Executor mainExecutor, @Background Executor bgExecutor) {
+            mAppClipsCrossProcessHelper = appClipsCrossProcessHelper;
+            mImageExporter = imageExporter;
+            mMainExecutor = mainExecutor;
+            mBgExecutor = bgExecutor;
+        }
+
+        @NonNull
+        @Override
+        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
+            if (modelClass != AppClipsViewModel.class) {
+                throw new IllegalArgumentException();
+            }
+
+            //noinspection unchecked
+            return (T) new AppClipsViewModel(mAppClipsCrossProcessHelper, mImageExporter,
+                    mMainExecutor, mBgExecutor);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/IAppClipsScreenshotHelperService.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/IAppClipsScreenshotHelperService.aidl
new file mode 100644
index 0000000..640e742
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/IAppClipsScreenshotHelperService.aidl
@@ -0,0 +1,29 @@
+/**
+ * Copyright (C) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.appclips;
+
+import android.os.Bundle;
+
+import com.android.systemui.screenshot.appclips.ScreenshotHardwareBufferInternal;
+
+/**
+ * A helper service that runs in SysUI process and helps {@link AppClipsActivity} which runs in its
+ * own separate process take a screenshot.
+ */
+interface IAppClipsScreenshotHelperService {
+    @nullable ScreenshotHardwareBufferInternal takeScreenshot(in int displayId);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/ScreenshotHardwareBufferInternal.aidl b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/ScreenshotHardwareBufferInternal.aidl
new file mode 100644
index 0000000..3a7b944
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/ScreenshotHardwareBufferInternal.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (C) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.appclips;
+
+parcelable ScreenshotHardwareBufferInternal;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/ScreenshotHardwareBufferInternal.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/ScreenshotHardwareBufferInternal.java
new file mode 100644
index 0000000..3b107f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/ScreenshotHardwareBufferInternal.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.appclips;
+
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.ParcelableColorSpace;
+import android.hardware.HardwareBuffer;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
+
+/**
+ * An internal version of {@link ScreenshotHardwareBuffer} that helps with parceling the information
+ * necessary for creating a {@link Bitmap}.
+ */
+public class ScreenshotHardwareBufferInternal implements Parcelable {
+
+    public static final Creator<ScreenshotHardwareBufferInternal> CREATOR =
+            new Creator<>() {
+                @Override
+                public ScreenshotHardwareBufferInternal createFromParcel(Parcel in) {
+                    return new ScreenshotHardwareBufferInternal(in);
+                }
+
+                @Override
+                public ScreenshotHardwareBufferInternal[] newArray(int size) {
+                    return new ScreenshotHardwareBufferInternal[size];
+                }
+            };
+    private final HardwareBuffer mHardwareBuffer;
+    private final ParcelableColorSpace mParcelableColorSpace;
+
+    public ScreenshotHardwareBufferInternal(
+            ScreenshotHardwareBuffer screenshotHardwareBuffer) {
+        mHardwareBuffer = screenshotHardwareBuffer.getHardwareBuffer();
+        mParcelableColorSpace = new ParcelableColorSpace(
+                screenshotHardwareBuffer.getColorSpace());
+    }
+
+    private ScreenshotHardwareBufferInternal(Parcel in) {
+        mHardwareBuffer = in.readParcelable(HardwareBuffer.class.getClassLoader(),
+                HardwareBuffer.class);
+        mParcelableColorSpace = in.readParcelable(ParcelableColorSpace.class.getClassLoader(),
+                ParcelableColorSpace.class);
+    }
+
+    /**
+     * Returns a {@link Bitmap} represented by the underlying data and successively closes the
+     * internal {@link HardwareBuffer}. See,
+     * {@link Bitmap#wrapHardwareBuffer(HardwareBuffer, ColorSpace)} and
+     * {@link HardwareBuffer#close()} for more information.
+     */
+    public Bitmap createBitmapThenCloseBuffer() {
+        Bitmap bitmap = Bitmap.wrapHardwareBuffer(mHardwareBuffer,
+                mParcelableColorSpace.getColorSpace());
+        mHardwareBuffer.close();
+        return bitmap;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mHardwareBuffer, flags);
+        dest.writeParcelable(mParcelableColorSpace, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof ScreenshotHardwareBufferInternal)) {
+            return false;
+        }
+
+        ScreenshotHardwareBufferInternal other = (ScreenshotHardwareBufferInternal) obj;
+        return mHardwareBuffer.equals(other.mHardwareBuffer) && mParcelableColorSpace.equals(
+                other.mParcelableColorSpace);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index fdb0100..22e238c0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -24,6 +24,8 @@
 import com.android.systemui.screenshot.ScreenshotPolicyImpl;
 import com.android.systemui.screenshot.ScreenshotProxyService;
 import com.android.systemui.screenshot.TakeScreenshotService;
+import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService;
+import com.android.systemui.screenshot.appclips.AppClipsService;
 
 import dagger.Binds;
 import dagger.Module;
@@ -52,4 +54,13 @@
     @Binds
     abstract ImageCapture bindImageCaptureImpl(ImageCaptureImpl capture);
 
+    @Binds
+    @IntoMap
+    @ClassKey(AppClipsScreenshotHelperService.class)
+    abstract Service bindAppClipsScreenshotHelperService(AppClipsScreenshotHelperService service);
+
+    @Binds
+    @IntoMap
+    @ClassKey(AppClipsService.class)
+    abstract Service bindAppClipsService(AppClipsService service);
 }
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 2c1e681..ed2772a 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -159,6 +159,12 @@
             android:enabled="false"
             tools:replace="android:authorities"
             android:grantUriPermissions="true" />
+
+        <activity
+            android:name="com.android.systemui.screenshot.appclips.AppClipsTrampolineActivityTest$AppClipsTrampolineActivityTestable"
+            android:exported="false"
+            android:permission="com.android.systemui.permission.SELF"
+            android:excludeFromRecents="true" />
     </application>
 
     <instrumentation android:name="android.testing.TestableInstrumentation"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index f40e3a6..003af80f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.media.dialog;
 
+import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_GO_TO_APP;
+import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_NONE;
+import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED;
+import static android.media.RouteListingPreference.Item.SUBTEXT_CUSTOM;
 import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -62,6 +66,7 @@
     private static final String TEST_DEVICE_ID_1 = "test_device_id_1";
     private static final String TEST_DEVICE_ID_2 = "test_device_id_2";
     private static final String TEST_SESSION_NAME = "test_session_name";
+    private static final String TEST_CUSTOM_SUBTEXT = "custom subtext";
 
     private static final int TEST_MAX_VOLUME = 20;
     private static final int TEST_CURRENT_VOLUME = 10;
@@ -431,12 +436,17 @@
     }
 
     @Test
-    public void subStatusSupported_onBindViewHolder_bindFailedStateDevice_verifyView() {
+    public void subStatusSupported_onBindViewHolder_bindDeviceRequirePremium_verifyView() {
         String deviceStatus = (String) mContext.getText(
                 R.string.media_output_status_require_premium);
         when(mMediaOutputController.isSubStatusSupported()).thenReturn(true);
-        when(mMediaDevice2.hasDisabledReason()).thenReturn(true);
-        when(mMediaDevice2.getDisableReason()).thenReturn(SUBTEXT_SUBSCRIPTION_REQUIRED);
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+        when(mMediaDevice2.hasSubtext()).thenReturn(true);
+        when(mMediaDevice2.getSubtext()).thenReturn(SUBTEXT_SUBSCRIPTION_REQUIRED);
+        when(mMediaDevice2.getSubtextString()).thenReturn(deviceStatus);
+        when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_GO_TO_APP);
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(new LinearLayout(mContext), 0);
         mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
 
         assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
@@ -444,9 +454,64 @@
         assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
         assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
         assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE);
         assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
         assertThat(mViewHolder.mSubTitleText.getText()).isEqualTo(deviceStatus);
         assertThat(mViewHolder.mTwoLineTitleText.getText()).isEqualTo(TEST_DEVICE_NAME_2);
+        assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isTrue();
+    }
+
+    @Test
+    public void subStatusSupported_onBindViewHolder_bindDeviceWithAdPlaying_verifyView() {
+        String deviceStatus = (String) mContext.getText(
+                R.string.media_output_status_try_after_ad);
+        when(mMediaOutputController.isSubStatusSupported()).thenReturn(true);
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+        when(mMediaDevice2.hasSubtext()).thenReturn(true);
+        when(mMediaDevice2.getSubtext()).thenReturn(SUBTEXT_AD_ROUTING_DISALLOWED);
+        when(mMediaDevice2.getSubtextString()).thenReturn(deviceStatus);
+        when(mMediaDevice2.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_NONE);
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(new LinearLayout(mContext), 0);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mSubTitleText.getText().toString()).isEqualTo(deviceStatus);
+        assertThat(mViewHolder.mTwoLineTitleText.getText().toString()).isEqualTo(
+                TEST_DEVICE_NAME_2);
+        assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isFalse();
+    }
+
+    @Test
+    public void subStatusSupported_onBindViewHolder_bindDeviceWithOngoingSession_verifyView() {
+        when(mMediaOutputController.isSubStatusSupported()).thenReturn(true);
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+        when(mMediaDevice1.hasSubtext()).thenReturn(true);
+        when(mMediaDevice1.getSubtext()).thenReturn(SUBTEXT_CUSTOM);
+        when(mMediaDevice1.getSubtextString()).thenReturn(TEST_CUSTOM_SUBTEXT);
+        when(mMediaDevice1.hasOngoingSession()).thenReturn(true);
+        when(mMediaDevice1.getSelectionBehavior()).thenReturn(SELECTION_BEHAVIOR_GO_TO_APP);
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(new LinearLayout(mContext), 0);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mSubTitleText.getText().toString()).isEqualTo(TEST_CUSTOM_SUBTEXT);
+        assertThat(mViewHolder.mTwoLineTitleText.getText().toString()).isEqualTo(
+                TEST_DEVICE_NAME_1);
+        assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isTrue();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 7c36e46..0bdcaf4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -240,7 +240,6 @@
         verify(mMediaController, never()).unregisterCallback(any());
     }
 
-
     @Test
     public void stop_nearbyMediaDevicesManagerNotNull_unregistersNearbyDevicesCallback() {
         mMediaOutputController.start(mCb);
@@ -253,7 +252,7 @@
 
     @Test
     public void tryToLaunchMediaApplication_nullIntent_skip() {
-        mMediaOutputController.tryToLaunchMediaApplication();
+        mMediaOutputController.tryToLaunchMediaApplication(mDialogLaunchView);
 
         verify(mCb, never()).dismissDialog();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
index a13bece..15545a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
@@ -29,9 +29,9 @@
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.content.pm.UserInfo;
 import android.os.Handler;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.dreams.IDreamManager;
 import android.service.quicksettings.Tile;
@@ -123,7 +123,7 @@
 
         // Should not be available if component is not set
         mSecureSettings.putInt(Settings.Secure.SCREENSAVER_ENABLED, 1);
-        when(mDreamManager.getDreamComponents()).thenReturn(null);
+        when(mDreamManager.getDreamComponentsForUser(mUserTracker.getUserId())).thenReturn(null);
 
         mTestableLooper.processAllMessages();
         assertEquals(Tile.STATE_UNAVAILABLE, mTile.getState().state);
@@ -134,9 +134,8 @@
     public void testInactiveWhenDreaming() throws RemoteException {
         setScreensaverEnabled(true);
 
-        when(mDreamManager.getDreamComponents()).thenReturn(new ComponentName[]{
-                COLORS_DREAM_COMPONENT_NAME
-        });
+        when(mDreamManager.getDreamComponentsForUser(mUserTracker.getUserId()))
+             .thenReturn(new ComponentName[]{COLORS_DREAM_COMPONENT_NAME});
         when(mDreamManager.isDreaming()).thenReturn(false);
 
         mTile.refreshState();
@@ -148,9 +147,8 @@
     public void testActive() throws RemoteException {
         setScreensaverEnabled(true);
 
-        when(mDreamManager.getDreamComponents()).thenReturn(new ComponentName[]{
-                COLORS_DREAM_COMPONENT_NAME
-        });
+        when(mDreamManager.getDreamComponentsForUser(mUserTracker.getUserId()))
+             .thenReturn(new ComponentName[]{COLORS_DREAM_COMPONENT_NAME});
         when(mDreamManager.isDreaming()).thenReturn(true);
 
         mTile.refreshState();
@@ -162,9 +160,8 @@
     public void testClick() throws RemoteException {
         // Set the AOSP dream enabled as the base setup.
         setScreensaverEnabled(true);
-        when(mDreamManager.getDreamComponents()).thenReturn(new ComponentName[]{
-                COLORS_DREAM_COMPONENT_NAME
-        });
+        when(mDreamManager.getDreamComponentsForUser(mUserTracker.getUserId()))
+             .thenReturn(new ComponentName[]{COLORS_DREAM_COMPONENT_NAME});
         when(mDreamManager.isDreaming()).thenReturn(false);
 
         mTile.refreshState();
@@ -203,21 +200,21 @@
 
         DreamTile supportedTileAllUsers = constructTileForTest(true, false);
 
-        UserHandle systemUserHandle = mock(UserHandle.class);
-        when(systemUserHandle.isSystem()).thenReturn(true);
+        UserInfo mainUserInfo = mock(UserInfo.class);
+        when(mainUserInfo.isMain()).thenReturn(true);
 
-        UserHandle nonSystemUserHandle = mock(UserHandle.class);
-        when(nonSystemUserHandle.isSystem()).thenReturn(false);
+        UserInfo nonMainUserInfo = mock(UserInfo.class);
+        when(nonMainUserInfo.isMain()).thenReturn(false);
 
-        when(mUserTracker.getUserHandle()).thenReturn(systemUserHandle);
+        when(mUserTracker.getUserInfo()).thenReturn(mainUserInfo);
         assertTrue(supportedTileAllUsers.isAvailable());
-        when(mUserTracker.getUserHandle()).thenReturn(nonSystemUserHandle);
+        when(mUserTracker.getUserInfo()).thenReturn(nonMainUserInfo);
         assertTrue(supportedTileAllUsers.isAvailable());
 
         DreamTile supportedTileOnlySystemUser = constructTileForTest(true, true);
-        when(mUserTracker.getUserHandle()).thenReturn(systemUserHandle);
+        when(mUserTracker.getUserInfo()).thenReturn(mainUserInfo);
         assertTrue(supportedTileOnlySystemUser.isAvailable());
-        when(mUserTracker.getUserHandle()).thenReturn(nonSystemUserHandle);
+        when(mUserTracker.getUserInfo()).thenReturn(nonMainUserInfo);
         assertFalse(supportedTileOnlySystemUser.isAvailable());
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperServiceTest.java
new file mode 100644
index 0000000..6e8f5fe
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsScreenshotHelperServiceTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.appclips;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.graphics.ColorSpace;
+import android.hardware.HardwareBuffer;
+import android.os.RemoteException;
+import android.view.Display;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
+import android.window.ScreenCapture.ScreenshotSync;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.wm.shell.bubbles.Bubbles;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@RunWith(AndroidJUnit4.class)
+public final class AppClipsScreenshotHelperServiceTest extends SysuiTestCase {
+
+    private static final Intent FAKE_INTENT = new Intent();
+    private static final int DEFAULT_DISPLAY = Display.DEFAULT_DISPLAY;
+    private static final HardwareBuffer FAKE_HARDWARE_BUFFER =
+            HardwareBuffer.create(1, 1, HardwareBuffer.RGBA_8888, 1,
+                    HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+    private static final ColorSpace FAKE_COLOR_SPACE = ColorSpace.get(ColorSpace.Named.SRGB);
+    private static final ScreenshotHardwareBufferInternal EXPECTED_SCREENSHOT_BUFFER =
+            new ScreenshotHardwareBufferInternal(
+                    new ScreenshotHardwareBuffer(FAKE_HARDWARE_BUFFER, FAKE_COLOR_SPACE, false,
+                            false));
+
+    @Mock private Optional<Bubbles> mBubblesOptional;
+    @Mock private Bubbles mBubbles;
+    @Mock private ScreenshotHardwareBuffer mScreenshotHardwareBuffer;
+    @Mock private ScreenshotSync mScreenshotSync;
+
+    private AppClipsScreenshotHelperService mAppClipsScreenshotHelperService;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mAppClipsScreenshotHelperService = new AppClipsScreenshotHelperService(mBubblesOptional);
+    }
+
+    @Test
+    public void emptyBubbles_shouldReturnNull() throws RemoteException {
+        when(mBubblesOptional.isEmpty()).thenReturn(true);
+
+        assertThat(getInterface().takeScreenshot(DEFAULT_DISPLAY)).isNull();
+    }
+
+    @Test
+    public void bubblesPresent_screenshotFailed_ShouldReturnNull() throws RemoteException {
+        when(mBubblesOptional.isEmpty()).thenReturn(false);
+        when(mBubblesOptional.get()).thenReturn(mBubbles);
+        when(mBubbles.getScreenshotExcludingBubble(DEFAULT_DISPLAY)).thenReturn(mScreenshotSync);
+        when(mScreenshotSync.get()).thenReturn(null);
+
+        assertThat(getInterface().takeScreenshot(DEFAULT_DISPLAY)).isNull();
+    }
+
+    @Test
+    public void bubblesPresent_screenshotSuccess_shouldReturnScreenshot() throws RemoteException {
+        when(mBubblesOptional.isEmpty()).thenReturn(false);
+        when(mBubblesOptional.get()).thenReturn(mBubbles);
+        when(mBubbles.getScreenshotExcludingBubble(DEFAULT_DISPLAY)).thenReturn(mScreenshotSync);
+        when(mScreenshotSync.get()).thenReturn(mScreenshotHardwareBuffer);
+        when(mScreenshotHardwareBuffer.getHardwareBuffer()).thenReturn(FAKE_HARDWARE_BUFFER);
+        when(mScreenshotHardwareBuffer.getColorSpace()).thenReturn(FAKE_COLOR_SPACE);
+
+        assertThat(getInterface().takeScreenshot(DEFAULT_DISPLAY)).isEqualTo(
+                EXPECTED_SCREENSHOT_BUFFER);
+    }
+
+    private IAppClipsScreenshotHelperService getInterface() {
+        return IAppClipsScreenshotHelperService.Stub.asInterface(
+                mAppClipsScreenshotHelperService.onBind(FAKE_INTENT));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
new file mode 100644
index 0000000..b55fe36
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.appclips;
+
+import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.statusbar.IAppClipsService;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.wm.shell.bubbles.Bubbles;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@RunWith(AndroidJUnit4.class)
+public final class AppClipsServiceTest extends SysuiTestCase {
+
+    private static final Intent FAKE_INTENT = new Intent();
+    private static final int FAKE_TASK_ID = 42;
+    private static final String EMPTY = "";
+
+    @Mock @Application private Context mMockContext;
+    @Mock private FeatureFlags mFeatureFlags;
+    @Mock private Optional<Bubbles> mOptionalBubbles;
+    @Mock private Bubbles mBubbles;
+    @Mock private DevicePolicyManager mDevicePolicyManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void flagOff_shouldReturnFalse() throws RemoteException {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(false);
+
+        assertThat(getInterfaceWithRealContext()
+                .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
+    }
+
+    @Test
+    public void emptyBubbles_shouldReturnFalse() throws RemoteException {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(true);
+
+        assertThat(getInterfaceWithRealContext()
+                .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
+    }
+
+    @Test
+    public void taskIdNotAppBubble_shouldReturnFalse() throws RemoteException {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(false);
+        when(mOptionalBubbles.get()).thenReturn(mBubbles);
+        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(false);
+
+        assertThat(getInterfaceWithRealContext()
+                .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
+    }
+
+    @Test
+    public void dpmScreenshotBlocked_shouldReturnFalse() throws RemoteException {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(false);
+        when(mOptionalBubbles.get()).thenReturn(mBubbles);
+        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
+        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(true);
+
+        assertThat(getInterfaceWithRealContext()
+                .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
+    }
+
+    @Test
+    public void configComponentNameNotValid_shouldReturnFalse() throws RemoteException {
+        when(mMockContext.getString(anyInt())).thenReturn(EMPTY);
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(false);
+        when(mOptionalBubbles.get()).thenReturn(mBubbles);
+        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
+        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
+
+        assertThat(getInterfaceWithMockContext()
+                .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isFalse();
+    }
+
+    @Test
+    public void allPrerequisitesSatisfy_shouldReturnTrue() throws RemoteException {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(false);
+        when(mOptionalBubbles.get()).thenReturn(mBubbles);
+        when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
+        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
+
+        assertThat(getInterfaceWithRealContext()
+                .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isTrue();
+    }
+
+    private IAppClipsService getInterfaceWithRealContext() {
+        AppClipsService appClipsService = new AppClipsService(getContext(), mFeatureFlags,
+                mOptionalBubbles, mDevicePolicyManager);
+        return getInterfaceFromService(appClipsService);
+    }
+
+    private IAppClipsService getInterfaceWithMockContext() {
+        AppClipsService appClipsService = new AppClipsService(mMockContext, mFeatureFlags,
+                mOptionalBubbles, mDevicePolicyManager);
+        return getInterfaceFromService(appClipsService);
+    }
+
+    private static IAppClipsService getInterfaceFromService(AppClipsService appClipsService) {
+        IBinder iBinder = appClipsService.onBind(FAKE_INTENT);
+        return IAppClipsService.Stub.asInterface(iBinder);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
new file mode 100644
index 0000000..295d127
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot.appclips;
+
+import static android.app.Instrumentation.ActivityResult;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED;
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
+import static android.content.Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE;
+
+import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
+import static com.android.systemui.screenshot.AppClipsTrampolineActivity.ACTION_FINISH_FROM_TRAMPOLINE;
+import static com.android.systemui.screenshot.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
+import static com.android.systemui.screenshot.AppClipsTrampolineActivity.PERMISSION_SELF;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.intercepting.SingleActivityFactory;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.notetask.NoteTaskController;
+import com.android.systemui.screenshot.AppClipsTrampolineActivity;
+import com.android.wm.shell.bubbles.Bubbles;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+@RunWith(AndroidTestingRunner.class)
+public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
+
+    private static final String TEST_URI_STRING = "www.test-uri.com";
+    private static final Uri TEST_URI = Uri.parse(TEST_URI_STRING);
+    private static final int TIME_OUT = 5000;
+
+    @Mock
+    private DevicePolicyManager mDevicePolicyManager;
+    @Mock
+    private FeatureFlags mFeatureFlags;
+    @Mock
+    private Optional<Bubbles> mOptionalBubbles;
+    @Mock
+    private Bubbles mBubbles;
+    @Mock
+    private NoteTaskController mNoteTaskController;
+    @Main
+    private Handler mMainHandler;
+
+    // Using the deprecated ActivityTestRule and SingleActivityFactory to help with injecting mocks
+    // and getting result from activity both of which are difficult to do in newer APIs.
+    private final SingleActivityFactory<AppClipsTrampolineActivityTestable> mFactory =
+            new SingleActivityFactory<>(AppClipsTrampolineActivityTestable.class) {
+                @Override
+                protected AppClipsTrampolineActivityTestable create(Intent unUsed) {
+                    return new AppClipsTrampolineActivityTestable(mDevicePolicyManager,
+                            mFeatureFlags, mOptionalBubbles, mNoteTaskController, mMainHandler);
+                }
+            };
+
+    @Rule
+    public final ActivityTestRule<AppClipsTrampolineActivityTestable> mActivityRule =
+            new ActivityTestRule<>(mFactory, false, false);
+
+    private Context mContext;
+    private Intent mActivityIntent;
+    private ComponentName mExpectedComponentName;
+    private Intent mKillAppClipsActivityBroadcast;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = getContext();
+        mMainHandler = mContext.getMainThreadHandler();
+
+        mActivityIntent = new Intent(mContext, AppClipsTrampolineActivityTestable.class);
+        mExpectedComponentName = ComponentName.unflattenFromString(
+                mContext.getString(
+                        R.string.config_screenshotAppClipsActivityComponent));
+        mKillAppClipsActivityBroadcast = new Intent(ACTION_FINISH_FROM_TRAMPOLINE)
+                .setComponent(mExpectedComponentName)
+                .setPackage(mExpectedComponentName.getPackageName());
+    }
+
+    @After
+    public void tearDown() {
+        mContext.sendBroadcast(mKillAppClipsActivityBroadcast, PERMISSION_SELF);
+        mActivityRule.finishActivity();
+    }
+
+    @Test
+    public void configComponentName_shouldResolve() {
+        // Verify component name is setup - has package and class name.
+        assertThat(mExpectedComponentName).isNotNull();
+        assertThat(mExpectedComponentName.getPackageName()).isNotEmpty();
+        assertThat(mExpectedComponentName.getClassName()).isNotEmpty();
+
+        // Verify an intent when launched with above component resolves to the same component to
+        // confirm that component from above is available in framework.
+        Intent appClipsActivityIntent = new Intent();
+        appClipsActivityIntent.setComponent(mExpectedComponentName);
+        ResolveInfo resolveInfo = getContext().getPackageManager().resolveActivity(
+                appClipsActivityIntent, PackageManager.ResolveInfoFlags.of(0));
+        ActivityInfo activityInfo = resolveInfo.activityInfo;
+
+        assertThat(activityInfo.packageName).isEqualTo(
+                mExpectedComponentName.getPackageName());
+        assertThat(activityInfo.name).isEqualTo(mExpectedComponentName.getClassName());
+    }
+
+    @Test
+    public void flagOff_shouldFinishWithResultCancel() {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(false);
+
+        mActivityRule.launchActivity(mActivityIntent);
+
+        assertThat(mActivityRule.getActivityResult().getResultCode())
+                .isEqualTo(Activity.RESULT_CANCELED);
+    }
+
+    @Test
+    public void bubblesEmpty_shouldFinishWithFailed() {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(true);
+
+        mActivityRule.launchActivity(mActivityIntent);
+
+        ActivityResult actualResult = mActivityRule.getActivityResult();
+        assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
+        assertThat(getStatusCodeExtra(actualResult.getResultData()))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+    }
+
+    @Test
+    public void taskIdNotAppBubble_shouldFinishWithWindowModeUnsupported() {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(false);
+        when(mOptionalBubbles.get()).thenReturn(mBubbles);
+        when(mBubbles.isAppBubbleTaskId(anyInt())).thenReturn(false);
+
+        mActivityRule.launchActivity(mActivityIntent);
+
+        ActivityResult actualResult = mActivityRule.getActivityResult();
+        assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
+        assertThat(getStatusCodeExtra(actualResult.getResultData()))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED);
+    }
+
+    @Test
+    public void dpmScreenshotBlocked_shouldFinishWithBlockedByAdmin() {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(false);
+        when(mOptionalBubbles.get()).thenReturn(mBubbles);
+        when(mBubbles.isAppBubbleTaskId(anyInt())).thenReturn(true);
+        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(true);
+
+        mActivityRule.launchActivity(mActivityIntent);
+
+        ActivityResult actualResult = mActivityRule.getActivityResult();
+        assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
+        assertThat(getStatusCodeExtra(actualResult.getResultData()))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_BLOCKED_BY_ADMIN);
+    }
+
+    @Test
+    public void startAppClipsActivity_userCanceled_shouldReturnUserCanceled() {
+        mockToSatisfyAllPrerequisites();
+
+        AppClipsTrampolineActivityTestable activity = mActivityRule.launchActivity(mActivityIntent);
+        waitForIdleSync();
+
+        Bundle bundle = new Bundle();
+        bundle.putInt(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE,
+                CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED);
+        activity.getResultReceiverForTest().send(Activity.RESULT_OK, bundle);
+        waitForIdleSync();
+
+        ActivityResult actualResult = mActivityRule.getActivityResult();
+        assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
+        assertThat(getStatusCodeExtra(actualResult.getResultData()))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_USER_CANCELED);
+    }
+
+    @Test
+    public void startAppClipsActivity_shouldReturnSuccess() {
+        mockToSatisfyAllPrerequisites();
+
+        AppClipsTrampolineActivityTestable activity = mActivityRule.launchActivity(mActivityIntent);
+        waitForIdleSync();
+
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(EXTRA_SCREENSHOT_URI, TEST_URI);
+        bundle.putInt(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
+        activity.getResultReceiverForTest().send(Activity.RESULT_OK, bundle);
+        waitForIdleSync();
+
+        ActivityResult actualResult = mActivityRule.getActivityResult();
+        assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
+        assertThat(getStatusCodeExtra(actualResult.getResultData()))
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
+        assertThat(actualResult.getResultData().getData()).isEqualTo(TEST_URI);
+    }
+
+    private void mockToSatisfyAllPrerequisites() {
+        when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
+        when(mOptionalBubbles.isEmpty()).thenReturn(false);
+        when(mOptionalBubbles.get()).thenReturn(mBubbles);
+        when(mBubbles.isAppBubbleTaskId(anyInt())).thenReturn(true);
+        when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
+    }
+
+    public static final class AppClipsTrampolineActivityTestable extends
+            AppClipsTrampolineActivity {
+
+        public AppClipsTrampolineActivityTestable(DevicePolicyManager devicePolicyManager,
+                FeatureFlags flags,
+                Optional<Bubbles> optionalBubbles,
+                NoteTaskController noteTaskController,
+                @Main Handler mainHandler) {
+            super(devicePolicyManager, flags, optionalBubbles, noteTaskController, mainHandler);
+        }
+    }
+
+    private static int getStatusCodeExtra(Intent intent) {
+        return intent.getIntExtra(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, -100);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
new file mode 100644
index 0000000..d5af7ce1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static android.content.Intent.CAPTURE_CONTENT_FOR_NOTE_FAILED;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.net.Uri;
+import android.os.UserHandle;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.screenshot.appclips.AppClipsCrossProcessHelper;
+
+import com.google.common.util.concurrent.Futures;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.time.ZonedDateTime;
+import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidJUnit4.class)
+public final class AppClipsViewModelTest extends SysuiTestCase {
+
+    private static final Bitmap FAKE_BITMAP = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
+    private static final Drawable FAKE_DRAWABLE = new ShapeDrawable();
+    private static final Rect FAKE_RECT = new Rect();
+    private static final Uri FAKE_URI = Uri.parse("www.test-uri.com");
+
+    @Mock private AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
+    @Mock private ImageExporter mImageExporter;
+
+    private com.android.systemui.screenshot.AppClipsViewModel mViewModel;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mViewModel = new AppClipsViewModel.Factory(mAppClipsCrossProcessHelper, mImageExporter,
+                getContext().getMainExecutor(), directExecutor()).create(AppClipsViewModel.class);
+    }
+
+    @Test
+    public void performScreenshot_fails_shouldUpdateErrorWithFailed() {
+        when(mAppClipsCrossProcessHelper.takeScreenshot()).thenReturn(null);
+
+        mViewModel.performScreenshot();
+        waitForIdleSync();
+
+        verify(mAppClipsCrossProcessHelper).takeScreenshot();
+        assertThat(mViewModel.getErrorLiveData().getValue())
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+        assertThat(mViewModel.getResultLiveData().getValue()).isNull();
+    }
+
+    @Test
+    public void performScreenshot_succeeds_shouldUpdateScreenshotWithBitmap() {
+        when(mAppClipsCrossProcessHelper.takeScreenshot()).thenReturn(FAKE_BITMAP);
+
+        mViewModel.performScreenshot();
+        waitForIdleSync();
+
+        verify(mAppClipsCrossProcessHelper).takeScreenshot();
+        assertThat(mViewModel.getErrorLiveData().getValue()).isNull();
+        assertThat(mViewModel.getScreenshot().getValue()).isEqualTo(FAKE_BITMAP);
+    }
+
+    @Test
+    public void saveScreenshot_throwsError_shouldUpdateErrorWithFailed() {
+        when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null), any(
+                ZonedDateTime.class), any(UserHandle.class))).thenReturn(
+                Futures.immediateFailedFuture(new ExecutionException(new Throwable())));
+
+        mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT);
+        waitForIdleSync();
+
+        assertThat(mViewModel.getErrorLiveData().getValue())
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+        assertThat(mViewModel.getResultLiveData().getValue()).isNull();
+    }
+
+    @Test
+    public void saveScreenshot_failsSilently_shouldUpdateErrorWithFailed() {
+        when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null), any(
+                ZonedDateTime.class), any(UserHandle.class))).thenReturn(
+                Futures.immediateFuture(new ImageExporter.Result()));
+
+        mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT);
+        waitForIdleSync();
+
+        assertThat(mViewModel.getErrorLiveData().getValue())
+                .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+        assertThat(mViewModel.getResultLiveData().getValue()).isNull();
+    }
+
+    @Test
+    public void saveScreenshot_succeeds_shouldUpdateResultWithUri() {
+        ImageExporter.Result result = new ImageExporter.Result();
+        result.uri = FAKE_URI;
+        when(mImageExporter.export(any(Executor.class), any(UUID.class), eq(null), any(
+                ZonedDateTime.class), any(UserHandle.class))).thenReturn(
+                Futures.immediateFuture(result));
+
+        mViewModel.saveScreenshotThenFinish(FAKE_DRAWABLE, FAKE_RECT);
+        waitForIdleSync();
+
+        assertThat(mViewModel.getErrorLiveData().getValue()).isNull();
+        assertThat(mViewModel.getResultLiveData().getValue()).isEqualTo(FAKE_URI);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 9d518ac..c6b4c92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -74,6 +74,7 @@
 import android.testing.TestableLooper;
 import android.util.Pair;
 import android.util.SparseArray;
+import android.view.IWindowManager;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
@@ -392,7 +393,8 @@
                 syncExecutor,
                 mock(Handler.class),
                 mTaskViewTransitions,
-                mock(SyncTransactionQueue.class));
+                mock(SyncTransactionQueue.class),
+                mock(IWindowManager.class));
         mBubbleController.setExpandListener(mBubbleExpandListener);
         spyOn(mBubbleController);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index 6357a09..3179285 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -20,6 +20,7 @@
 import android.content.pm.LauncherApps;
 import android.os.Handler;
 import android.os.UserManager;
+import android.view.IWindowManager;
 import android.view.WindowManager;
 
 import com.android.internal.statusbar.IStatusBarService;
@@ -72,13 +73,14 @@
             ShellExecutor shellMainExecutor,
             Handler shellMainHandler,
             TaskViewTransitions taskViewTransitions,
-            SyncTransactionQueue syncQueue) {
+            SyncTransactionQueue syncQueue,
+            IWindowManager wmService) {
         super(context, shellInit, shellCommandHandler, shellController, data, Runnable::run,
                 floatingContentCoordinator, dataRepository, statusBarService, windowManager,
                 windowManagerShellWrapper, userManager, launcherApps, bubbleLogger,
                 taskStackListener, shellTaskOrganizer, positioner, displayController,
                 oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler,
-                new SyncExecutor(), taskViewTransitions, syncQueue);
+                new SyncExecutor(), taskViewTransitions, syncQueue, wmService);
         setInflateSynchronously(true);
         onInit();
     }
diff --git a/services/art-profile b/services/art-profile
index 2bb85a4..132b9ab 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -20484,12 +20484,12 @@
 Lcom/android/server/usage/UsageStatsShellCommand;
 Lcom/android/server/usage/UserUsageStatsService$StatsUpdatedListener;
 Lcom/android/server/usb/UsbAlsaJackDetector;
+Lcom/android/server/usb/UsbAlsaMidiDevice$2;
+Lcom/android/server/usb/UsbAlsaMidiDevice$3;
+Lcom/android/server/usb/UsbAlsaMidiDevice$InputReceiverProxy;
+Lcom/android/server/usb/UsbAlsaMidiDevice;
 Lcom/android/server/usb/UsbDeviceManager;
 Lcom/android/server/usb/UsbHostManager;
-Lcom/android/server/usb/UsbMidiDevice$2;
-Lcom/android/server/usb/UsbMidiDevice$3;
-Lcom/android/server/usb/UsbMidiDevice$InputReceiverProxy;
-Lcom/android/server/usb/UsbMidiDevice;
 Lcom/android/server/usb/descriptors/UsbDescriptor;
 Lcom/android/server/usb/descriptors/UsbInterfaceDescriptor;
 Lcom/android/server/utils/AlarmQueue$1;
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index ef79351..cdc5d81 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -770,10 +770,6 @@
     // initialized in the constructor.
     public int CUR_MAX_EMPTY_PROCESSES;
 
-    /** @see mEnforceReceiverExportedFlagRequirement */
-    private static final String KEY_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT =
-            "enforce_exported_flag_requirement";
-
     /** @see #mNoKillCachedProcessesUntilBootCompleted */
     private static final String KEY_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED =
             "no_kill_cached_processes_until_boot_completed";
@@ -782,9 +778,6 @@
     private static final String KEY_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS =
             "no_kill_cached_processes_post_boot_completed_duration_millis";
 
-    /** @see mEnforceReceiverExportedFlagRequirement */
-    private static final boolean DEFAULT_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT = true;
-
     /** @see #mNoKillCachedProcessesUntilBootCompleted */
     private static final boolean DEFAULT_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED = true;
 
@@ -793,15 +786,6 @@
             DEFAULT_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS = 600_000;
 
     /**
-     * If true, enforce the requirement that dynamically registered receivers specify one of
-     * {@link android.content.Context#RECEIVER_EXPORTED} or
-     * {@link android.content.Context#RECEIVER_NOT_EXPORTED} if registering for any non-system
-     * broadcasts.
-     */
-    volatile boolean mEnforceReceiverExportedFlagRequirement =
-            DEFAULT_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT;
-
-    /**
      * If true, do not kill excessive cached processes proactively, until user-0 is unlocked.
      * @see #mNoKillCachedProcessesPostBootCompletedDurationMillis
      */
@@ -1112,9 +1096,6 @@
                             case KEY_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED:
                                 updateNoKillCachedProcessesUntilBootCompleted();
                                 break;
-                            case KEY_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT:
-                                updateEnforceReceiverExportedFlagRequirement();
-                                break;
                             case KEY_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS:
                                 updateNoKillCachedProcessesPostBootCompletedDurationMillis();
                                 break;
@@ -1616,13 +1597,6 @@
                 DEFAULT_DEFER_BOOT_COMPLETED_BROADCAST);
     }
 
-    private void updateEnforceReceiverExportedFlagRequirement() {
-        mEnforceReceiverExportedFlagRequirement = DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
-                KEY_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT,
-                DEFAULT_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT);
-    }
-
     private void updateNoKillCachedProcessesUntilBootCompleted() {
         mNoKillCachedProcessesUntilBootCompleted = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -2002,8 +1976,6 @@
         pw.print("="); pw.println(mPrioritizeAlarmBroadcasts);
         pw.print("  "); pw.print(KEY_NO_KILL_CACHED_PROCESSES_UNTIL_BOOT_COMPLETED);
         pw.print("="); pw.println(mNoKillCachedProcessesUntilBootCompleted);
-        pw.print("  "); pw.print(KEY_ENFORCE_RECEIVER_EXPORTED_FLAG_REQUIREMENT);
-        pw.print("="); pw.println(mEnforceReceiverExportedFlagRequirement);
         pw.print("  "); pw.print(KEY_NO_KILL_CACHED_PROCESSES_POST_BOOT_COMPLETED_DURATION_MILLIS);
         pw.print("="); pw.println(mNoKillCachedProcessesPostBootCompletedDurationMillis);
         pw.print("  "); pw.print(KEY_MAX_EMPTY_TIME_MILLIS);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 423a090..b272502 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13588,8 +13588,7 @@
             // broadcasts, or the receiver is null (a sticky broadcast). Sticky broadcasts should
             // not be used generally, so we will be marking them as exported by default
             boolean requireExplicitFlagForDynamicReceivers = CompatChanges.isChangeEnabled(
-                    DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid)
-                    && mConstants.mEnforceReceiverExportedFlagRequirement;
+                    DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED, callingUid);
             // STOPSHIP(b/259139792): Allow apps that are currently targeting U and in process of
             // updating their receivers to be exempt from this requirement until their receivers
             // are flagged.
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index ef0de18..704b425 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -106,6 +106,21 @@
     }
 
     @Override
+    public SparseIntArray getNonDefaultPackageModes(String packageName, int userId) {
+        synchronized (mLock) {
+            ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId);
+            if (packageModes == null) {
+                return new SparseIntArray();
+            }
+            SparseIntArray opModes = packageModes.get(packageName);
+            if (opModes == null) {
+                return new SparseIntArray();
+            }
+            return opModes.clone();
+        }
+    }
+
+    @Override
     public int getUidMode(int uid, int op) {
         synchronized (mLock) {
             SparseIntArray opModes = mUidModes.get(uid, null);
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
index d8d0d48..9a564fc 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -39,6 +39,15 @@
     SparseIntArray getNonDefaultUidModes(int uid);
 
     /**
+     * Returns a copy of non-default app-ops with op as keys and their modes as values for a package
+     * and user.
+     * Returns an empty SparseIntArray if nothing is set.
+     * @param packageName for which we need the app-ops and their modes.
+     * @param userId for which the package is installed in.
+     */
+    SparseIntArray getNonDefaultPackageModes(String packageName, int userId);
+
+    /**
      * Returns the app-op mode for a particular app-op of a uid.
      * Returns default op mode if the op mode for particular uid and op is not set.
      * @param uid user id for which we need the mode.
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java
index ac479b2..b8326ad 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceLoggingDecorator.java
@@ -46,6 +46,13 @@
     }
 
     @Override
+    public SparseIntArray getNonDefaultPackageModes(String packageName, int userId) {
+        Log.i(LOG_TAG, "getNonDefaultPackageModes("
+                + "packageName = " + packageName + ", userId = " + userId + ") ");
+        return mService.getNonDefaultPackageModes(packageName, userId);
+    }
+
+    @Override
     public int getUidMode(int uid, int op) {
         Log.i(LOG_TAG, "getUidMode(uid = " + uid + ", op = " + op + ")");
         return mService.getUidMode(uid, op);
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index c50f2b7..af6470f 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -98,6 +98,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PermissionInfo;
+import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
 import android.net.Uri;
@@ -162,6 +163,7 @@
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.component.ParsedAttribution;
 import com.android.server.policy.AppOpsPolicy;
 
@@ -383,6 +385,9 @@
     /** Package Manager internal. Access via {@link #getPackageManagerInternal()} */
     private @Nullable PackageManagerInternal mPackageManagerInternal;
 
+    /** User Manager internal. Access via {@link #getUserManagerInternal()} */
+    private @Nullable UserManagerInternal mUserManagerInternal;
+
     /** Interface for app-op modes.*/
     @VisibleForTesting
     AppOpsCheckingServiceInterface mAppOpsCheckingService;
@@ -525,22 +530,6 @@
             pkgOps = null;
         }
 
-        public boolean isDefault() {
-            boolean areAllPackageModesDefault = true;
-            if (pkgOps != null) {
-                for (String packageName : pkgOps.keySet()) {
-                    if (!mAppOpsCheckingService.arePackageModesDefault(packageName,
-                            UserHandle.getUserId(uid))) {
-                        areAllPackageModesDefault = false;
-                        break;
-                    }
-                }
-            }
-            return (pkgOps == null || pkgOps.isEmpty())
-                    && mAppOpsCheckingService.areUidModesDefault(uid)
-                    && areAllPackageModesDefault;
-        }
-
         // Functions for uid mode access and manipulation.
         public SparseIntArray getNonDefaultUidModes() {
             return mAppOpsCheckingService.getNonDefaultUidModes(uid);
@@ -1076,6 +1065,17 @@
         synchronized (this) {
             upgradeLocked(mVersionAtBoot);
         }
+        initializeUidStates();
+
+        getUserManagerInternal().addUserLifecycleListener(
+                new UserManagerInternal.UserLifecycleListener() {
+                    @Override
+                    public void onUserCreated(UserInfo user, Object token) {
+                        initializeUserUidStates(user.id);
+                    }
+
+                    // onUserRemoved handled by #removeUser
+                });
 
         mConstants.startMonitoring(mContext.getContentResolver());
         mHistoricalRegistry.systemReady(mContext.getContentResolver());
@@ -1203,6 +1203,49 @@
     }
 
     /**
+     * Initialize uid state objects for state contained in the checking service.
+     */
+    private void initializeUidStates() {
+        UserManagerInternal umi = getUserManagerInternal();
+        int[] userIds = umi.getUserIds();
+        synchronized (this) {
+            for (int i = 0; i < userIds.length; i++) {
+                int userId = userIds[i];
+                initializeUserUidStatesLocked(userId);
+            }
+        }
+    }
+
+    private void initializeUserUidStates(int userId) {
+        synchronized (this) {
+            initializeUserUidStatesLocked(userId);
+        }
+    }
+
+    private void initializeUserUidStatesLocked(int userId) {
+        ArrayMap<String, ? extends PackageStateInternal> packageStates =
+                getPackageManagerInternal().getPackageStates();
+        for (int j = 0; j < packageStates.size(); j++) {
+            PackageStateInternal packageState = packageStates.valueAt(j);
+            int uid = UserHandle.getUid(userId, packageState.getAppId());
+            UidState uidState = getUidStateLocked(uid, true);
+            if (uidState.pkgOps == null) {
+                uidState.pkgOps = new ArrayMap<>();
+            }
+            String packageName = packageStates.keyAt(j);
+            Ops ops = new Ops(packageName, uidState);
+            uidState.pkgOps.put(packageName, ops);
+
+            SparseIntArray packageModes =
+                    mAppOpsCheckingService.getNonDefaultPackageModes(packageName, userId);
+            for (int k = 0; k < packageModes.size(); k++) {
+                int code = packageModes.get(k);
+                ops.put(code, new Op(uidState, packageName, code, uid));
+            }
+        }
+    }
+
+    /**
      * Sets a policy for handling app ops.
      *
      * @param policy The policy.
@@ -1687,13 +1730,6 @@
                         pkgOps.remove(ops.packageName);
                         mAppOpsCheckingService.removePackage(ops.packageName,
                                 UserHandle.getUserId(uidState.uid));
-                        if (pkgOps.isEmpty()) {
-                            uidState.pkgOps = null;
-                        }
-                        if (uidState.isDefault()) {
-                            uidState.clear();
-                            mUidStates.remove(uid);
-                        }
                     }
                 }
             }
@@ -2147,10 +2183,6 @@
                                 UserHandle.getUserId(uidState.uid));
                     }
                 }
-                if (uidState.isDefault()) {
-                    uidState.clear();
-                    mUidStates.remove(uidState.uid);
-                }
                 if (uidChanged) {
                     uidState.evalForegroundOps();
                 }
@@ -3588,6 +3620,20 @@
     }
 
     /**
+     * @return {@link UserManagerInternal}
+     */
+    private @NonNull UserManagerInternal getUserManagerInternal() {
+        if (mUserManagerInternal == null) {
+            mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+        }
+        if (mUserManagerInternal == null) {
+            throw new IllegalStateException("UserManagerInternal not loaded");
+        }
+
+        return mUserManagerInternal;
+    }
+
+    /**
      * Create a restriction description matching the properties of the package.
      *
      * @param pkg The package to create the restriction description for
diff --git a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
index dce1c96..39c649b 100644
--- a/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
+++ b/services/core/java/com/android/server/clipboard/EmulatorClipboardMonitor.java
@@ -111,7 +111,7 @@
         bb.order(ByteOrder.LITTLE_ENDIAN);
         final int msgLen = bb.getInt();
 
-        if (msgLen <= 0 || msgLen > MAX_CLIPBOARD_BYTES) {
+        if (msgLen < 0 || msgLen > MAX_CLIPBOARD_BYTES) {
             throw new ProtocolException("Clipboard message length: " + msgLen + " out of bounds.");
         }
 
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 3759a8b..70069c6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -245,7 +245,7 @@
     private Display.Mode mUserPreferredMode;
     // HDR conversion mode chosen by user
     @GuardedBy("mSyncRoot")
-    private HdrConversionMode mHdrConversionMode;
+    private HdrConversionMode mHdrConversionMode = null;
 
     // The synchronization root for the display manager.
     // This lock guards most of the display manager's state.
@@ -647,6 +647,7 @@
             updateSettingsLocked();
             updateUserDisabledHdrTypesFromSettingsLocked();
             updateUserPreferredDisplayModeSettingsLocked();
+            updateHdrConversionModeSettingsLocked();
         }
 
         mDisplayModeDirector.setDesiredDisplayModeSpecsListener(
@@ -1806,6 +1807,31 @@
         device.setUserPreferredDisplayModeLocked(modeBuilder.build());
     }
 
+    @GuardedBy("mSyncRoot")
+    private void storeHdrConversionModeLocked(HdrConversionMode hdrConversionMode) {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.HDR_CONVERSION_MODE, hdrConversionMode.getConversionMode());
+        final int preferredHdrOutputType =
+                hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_FORCE
+                        ? hdrConversionMode.getPreferredHdrOutputType()
+                        : Display.HdrCapabilities.HDR_TYPE_INVALID;
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.HDR_FORCE_CONVERSION_TYPE, preferredHdrOutputType);
+    }
+
+    @GuardedBy("mSyncRoot")
+    void updateHdrConversionModeSettingsLocked() {
+        final int conversionMode = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.HDR_CONVERSION_MODE, HdrConversionMode.HDR_CONVERSION_SYSTEM);
+        final int preferredHdrOutputType = conversionMode == HdrConversionMode.HDR_CONVERSION_FORCE
+                ? Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.HDR_FORCE_CONVERSION_TYPE,
+                        Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION)
+                : Display.HdrCapabilities.HDR_TYPE_INVALID;
+        mHdrConversionMode = new HdrConversionMode(conversionMode, preferredHdrOutputType);
+        setHdrConversionModeInternal(mHdrConversionMode);
+    }
+
     // If we've never recorded stable device stats for this device before and they aren't
     // explicitly configured, go ahead and record the stable device stats now based on the status
     // of the default display at first boot.
@@ -1962,13 +1988,14 @@
         int[] autoHdrOutputTypes = null;
         synchronized (mSyncRoot) {
             mHdrConversionMode = hdrConversionMode;
+            storeHdrConversionModeLocked(mHdrConversionMode);
 
             // For auto mode, all supported HDR types are allowed except the ones specifically
             // disabled by the user.
             if (hdrConversionMode.getConversionMode() == HdrConversionMode.HDR_CONVERSION_SYSTEM) {
                 autoHdrOutputTypes = getEnabledAutoHdrTypesLocked();
             }
-            DisplayControl.setHdrConversionMode(hdrConversionMode.getConversionMode(),
+            mInjector.setHdrConversionMode(hdrConversionMode.getConversionMode(),
                     hdrConversionMode.getPreferredHdrOutputType(), autoHdrOutputTypes);
         }
     }
@@ -1984,7 +2011,7 @@
 
     private @Display.HdrCapabilities.HdrType int[] getSupportedHdrOutputTypesInternal() {
         if (mSupportedHdrOutputType == null) {
-            mSupportedHdrOutputType = DisplayControl.getSupportedHdrOutputTypes();
+            mSupportedHdrOutputType = mInjector.getSupportedHdrOutputTypes();
         }
         return mSupportedHdrOutputType;
     }
@@ -2604,6 +2631,10 @@
                 }
             }
 
+            if (mHdrConversionMode != null) {
+                pw.println("  mHdrConversionMode=" + mHdrConversionMode);
+            }
+
             pw.println();
             final int displayStateCount = mDisplayStates.size();
             pw.println("Display States: size=" + displayStateCount);
@@ -2712,6 +2743,16 @@
         long getDefaultDisplayDelayTimeout() {
             return WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT;
         }
+
+        void setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
+                int[] autoHdrTypes) {
+            DisplayControl.setHdrConversionMode(conversionMode, preferredHdrOutputType,
+                    autoHdrTypes);
+        }
+
+        int[] getSupportedHdrOutputTypes() {
+            return DisplayControl.getSupportedHdrOutputTypes();
+        }
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index d9cdba7..caa5036 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -72,6 +72,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.input.InputManagerInternal;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.ActivityInterceptorCallback;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
@@ -639,8 +640,10 @@
     }
 
     private boolean dreamsEnabledForUser(int userId) {
-        // TODO(b/257333623): Support non-system Dock Users in HSUM.
-        return !mDreamsOnlyEnabledForDockUser || (userId == UserHandle.USER_SYSTEM);
+        if (!mDreamsOnlyEnabledForDockUser) return true;
+        if (userId < 0) return false;
+        final int mainUserId = LocalServices.getService(UserManagerInternal.class).getMainUserId();
+        return userId == mainUserId;
     }
 
     private ServiceInfo getServiceInfo(ComponentName name) {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index ebc18bc..cd4a8f3 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -110,6 +110,7 @@
 import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
 import android.security.keystore2.AndroidKeyStoreProvider;
 import android.service.gatekeeper.IGateKeeperService;
+import android.service.notification.StatusBarNotification;
 import android.system.keystore2.Domain;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -581,6 +582,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(Intent.ACTION_USER_ADDED);
         filter.addAction(Intent.ACTION_USER_STARTING);
+        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
         injector.getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter,
                 null, null);
 
@@ -602,6 +604,20 @@
         LocalServices.addService(LockSettingsInternal.class, new LocalService());
     }
 
+    private void updateActivatedEncryptionNotifications(String reason) {
+        for (UserInfo userInfo : mUserManager.getUsers()) {
+            Context userContext = mContext.createContextAsUser(UserHandle.of(userInfo.id), 0);
+            NotificationManager nm = (NotificationManager)
+                    userContext.getSystemService(Context.NOTIFICATION_SERVICE);
+            for (StatusBarNotification notification : nm.getActiveNotifications()) {
+                if (notification.getId() == SystemMessage.NOTE_FBE_ENCRYPTED_NOTIFICATION) {
+                    maybeShowEncryptionNotificationForUser(userInfo.id, reason);
+                    break;
+                }
+            }
+        }
+    }
+
     /**
      * If the account is credential-encrypted, show notification requesting the user to unlock the
      * device.
@@ -799,6 +815,8 @@
             } else if (Intent.ACTION_USER_STARTING.equals(intent.getAction())) {
                 final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
                 mStorage.prefetchUser(userHandle);
+            } else if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
+                updateActivatedEncryptionNotifications("locale changed");
             }
         }
     };
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 0d5392b..1ed5999 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -101,6 +101,7 @@
 import android.apex.ApexInfo;
 import android.app.AppOpsManager;
 import android.app.ApplicationPackageManager;
+import android.app.BroadcastOptions;
 import android.app.backup.IBackupManager;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -687,7 +688,10 @@
         fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
                 PackageManager.installStatusToPublicStatus(returnCode));
         try {
-            target.sendIntent(context, 0, fillIn, null, null);
+            final BroadcastOptions options = BroadcastOptions.makeBasic();
+            options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+            target.sendIntent(context, 0, fillIn, null /* onFinished*/, null /* handler */,
+                    null /* requiredPermission */, options.toBundle());
         } catch (IntentSender.SendIntentException ignored) {
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 6546f6a..8823714 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -28,6 +28,7 @@
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PackageDeleteObserver;
@@ -1314,7 +1315,10 @@
             intent.putExtra(PackageInstaller.EXTRA_INSTALL_CONSTRAINTS, constraints);
             intent.putExtra(PackageInstaller.EXTRA_INSTALL_CONSTRAINTS_RESULT, result);
             try {
-                callback.sendIntent(mContext, 0, intent, null, null);
+                final BroadcastOptions options = BroadcastOptions.makeBasic();
+                options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+                callback.sendIntent(mContext, 0, intent, null /* onFinished*/,
+                        null /* handler */, null /* requiredPermission */, options.toBundle());
             } catch (SendIntentException ignore) {
             }
         });
@@ -1477,7 +1481,10 @@
                     PackageInstaller.STATUS_PENDING_USER_ACTION);
             fillIn.putExtra(Intent.EXTRA_INTENT, intent);
             try {
-                mTarget.sendIntent(mContext, 0, fillIn, null, null);
+                final BroadcastOptions options = BroadcastOptions.makeBasic();
+                options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+                mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/,
+                        null /* handler */, null /* requiredPermission */, options.toBundle());
             } catch (SendIntentException ignored) {
             }
         }
@@ -1502,7 +1509,10 @@
                     PackageManager.deleteStatusToString(returnCode, msg));
             fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode);
             try {
-                mTarget.sendIntent(mContext, 0, fillIn, null, null);
+                final BroadcastOptions options = BroadcastOptions.makeBasic();
+                options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+                mTarget.sendIntent(mContext, 0, fillIn, null /* onFinished*/,
+                        null /* handler */, null /* requiredPermission */, options.toBundle());
             } catch (SendIntentException ignored) {
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 47e18f1..f77d38f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -59,6 +59,7 @@
 import android.annotation.Nullable;
 import android.annotation.WorkerThread;
 import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.admin.DevicePolicyEventLogger;
@@ -4495,8 +4496,10 @@
         intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_SUCCESS);
         intent.putExtra(PackageInstaller.EXTRA_PRE_APPROVAL, true);
         try {
+            final BroadcastOptions options = BroadcastOptions.makeBasic();
+            options.setPendingIntentBackgroundActivityLaunchAllowed(false);
             target.sendIntent(mContext, 0 /* code */, intent, null /* onFinished */,
-                    null /* handler */);
+                    null /* handler */, null /* requiredPermission */, options.toBundle());
         } catch (IntentSender.SendIntentException ignored) {
         }
     }
@@ -4763,7 +4766,10 @@
                 PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(intent.getAction()));
         fillIn.putExtra(Intent.EXTRA_INTENT, intent);
         try {
-            target.sendIntent(context, 0, fillIn, null, null);
+            final BroadcastOptions options = BroadcastOptions.makeBasic();
+            options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+            target.sendIntent(context, 0, fillIn, null /* onFinished */,
+                    null /* handler */, null /* requiredPermission */, options.toBundle());
         } catch (IntentSender.SendIntentException ignored) {
         }
     }
@@ -4805,7 +4811,10 @@
             }
         }
         try {
-            target.sendIntent(context, 0, fillIn, null, null);
+            final BroadcastOptions options = BroadcastOptions.makeBasic();
+            options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+            target.sendIntent(context, 0, fillIn, null /* onFinished */,
+                    null /* handler */, null /* requiredPermission */, options.toBundle());
         } catch (IntentSender.SendIntentException ignored) {
         }
     }
@@ -4839,7 +4848,10 @@
             intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, "Staging Image Not Ready");
         }
         try {
-            target.sendIntent(context, 0, intent, null, null);
+            final BroadcastOptions options = BroadcastOptions.makeBasic();
+            options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+            target.sendIntent(context, 0, intent, null /* onFinished */,
+                    null /* handler */, null /* requiredPermission */, options.toBundle());
         } catch (IntentSender.SendIntentException ignored) {
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5d41b4c..d0a0558 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -56,6 +56,7 @@
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.ApplicationPackageManager;
+import android.app.BroadcastOptions;
 import android.app.IActivityManager;
 import android.app.admin.IDevicePolicyManager;
 import android.app.admin.SecurityLog;
@@ -4948,7 +4949,11 @@
                 }
                 if (pi != null) {
                     try {
-                        pi.sendIntent(null, success ? 1 : 0, null, null, null);
+                        final BroadcastOptions options = BroadcastOptions.makeBasic();
+                        options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+                        pi.sendIntent(null, success ? 1 : 0, null /* intent */,
+                                null /* onFinished*/, null /* handler */,
+                                null /* requiredPermission */, options.toBundle());
                     } catch (SendIntentException e) {
                         Slog.w(TAG, e);
                     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f592f9a..4174bfe 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -46,7 +46,7 @@
 import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
 import static android.view.Display.STATE_UNKNOWN;
 import static android.view.Display.isSuspendedState;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
 import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
 import static android.view.Surface.ROTATION_0;
@@ -120,12 +120,10 @@
 import static com.android.server.wm.DisplayContentProto.FOCUSED_APP;
 import static com.android.server.wm.DisplayContentProto.FOCUSED_ROOT_TASK_ID;
 import static com.android.server.wm.DisplayContentProto.ID;
-import static com.android.server.wm.DisplayContentProto.IME_INSETS_SOURCE_PROVIDER;
 import static com.android.server.wm.DisplayContentProto.IME_POLICY;
 import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_CONTROL_TARGET;
 import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_INPUT_TARGET;
 import static com.android.server.wm.DisplayContentProto.INPUT_METHOD_TARGET;
-import static com.android.server.wm.DisplayContentProto.INSETS_SOURCE_PROVIDERS;
 import static com.android.server.wm.DisplayContentProto.IS_SLEEPING;
 import static com.android.server.wm.DisplayContentProto.KEEP_CLEAR_AREAS;
 import static com.android.server.wm.DisplayContentProto.MIN_SIZE_OF_RESIZEABLE_TASK_DP;
@@ -293,6 +291,13 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface ForceScalingMode {}
 
+    private static final InsetsState.OnTraverseCallbacks COPY_SOURCE_VISIBILITY =
+            new InsetsState.OnTraverseCallbacks() {
+                public void onIdMatch(InsetsSource source1, InsetsSource source2) {
+                    source1.setVisible(source2.isVisible());
+                }
+            };
+
     final ActivityTaskManagerService mAtmService;
 
     /**
@@ -2088,12 +2093,7 @@
                     mFixedRotationLaunchingApp.getFixedRotationTransformInsetsState();
             if (rotatedState != null) {
                 final InsetsState state = mInsetsStateController.getRawInsetsState();
-                for (int i = 0; i < InsetsState.SIZE; i++) {
-                    final InsetsSource source = state.peekSource(i);
-                    if (source != null) {
-                        rotatedState.setSourceVisible(i, source.isVisible());
-                    }
-                }
+                InsetsState.traverse(rotatedState, state, COPY_SOURCE_VISIBILITY);
             }
         }
         forAllWindows(dispatchInsetsChanged, true /* traverseTopToBottom */);
@@ -3377,7 +3377,7 @@
 
     int getInputMethodWindowVisibleHeight() {
         final InsetsState state = getInsetsStateController().getRawInsetsState();
-        final InsetsSource imeSource = state.peekSource(ITYPE_IME);
+        final InsetsSource imeSource = state.peekSource(ID_IME);
         if (imeSource == null || !imeSource.isVisible()) {
             return 0;
         }
@@ -3544,14 +3544,7 @@
             mCurrentFocus.dumpDebug(proto, CURRENT_FOCUS, logLevel);
         }
         if (mInsetsStateController != null) {
-            for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
-                final WindowContainerInsetsSourceProvider provider = mInsetsStateController
-                        .peekSourceProvider(type);
-                if (provider != null) {
-                    provider.dumpDebug(proto, type == ITYPE_IME ? IME_INSETS_SOURCE_PROVIDER :
-                            INSETS_SOURCE_PROVIDERS, logLevel);
-                }
-            }
+            mInsetsStateController.dumpDebug(proto, logLevel);
         }
         proto.write(IME_POLICY, getImePolicy());
         for (Rect r : getKeepClearAreas()) {
@@ -4038,7 +4031,7 @@
             final int imePid = mInputMethodWindow.mSession.mPid;
             mAtmService.onImeWindowSetOnDisplayArea(imePid, mImeWindowsContainer);
         }
-        mInsetsStateController.getSourceProvider(ITYPE_IME).setWindowContainer(win,
+        mInsetsStateController.getSourceProvider(ID_IME).setWindowContainer(win,
                 mDisplayPolicy.getImeSourceFrameProvider(), null);
         computeImeTarget(true /* updateImeTarget */);
         updateImeControlTarget();
@@ -4287,7 +4280,8 @@
         assignWindowLayers(true /* setLayoutNeeded */);
         // 3. The z-order of IME might have been changed. Update the above insets state.
         mInsetsStateController.updateAboveInsetsState(
-                mInsetsStateController.getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
+                mInsetsStateController.getRawInsetsState().isSourceOrDefaultVisible(
+                        ID_IME, ime()));
         // 4. Update the IME control target to apply any inset change and animation.
         // 5. Reparent the IME container surface to either the input target app, or the IME window
         // parent.
@@ -4513,7 +4507,7 @@
             ProtoLog.i(WM_DEBUG_IME, "setInputMethodInputTarget %s", target);
             setImeInputTarget(target);
             mInsetsStateController.updateAboveInsetsState(mInsetsStateController
-                    .getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
+                    .getRawInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
             // Force updating the IME parent when the IME control target has been updated to the
             // remote target but updateImeParent not happen because ImeLayeringTarget and
             // ImeInputTarget are different. Then later updateImeParent would be ignored when there
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index e984456..7f785af 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -16,10 +16,8 @@
 
 package com.android.server.wm;
 
-import static android.view.InsetsState.ITYPE_BOTTOM_DISPLAY_CUTOUT;
-import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT;
-import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT;
-import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT;
+import static android.view.InsetsSource.createId;
+import static android.view.WindowInsets.Type.displayCutout;
 
 import android.annotation.NonNull;
 import android.graphics.Rect;
@@ -38,6 +36,12 @@
  * @hide
  */
 public class DisplayFrames {
+
+    private static final int ID_DISPLAY_CUTOUT_LEFT = createId(null, 0, displayCutout());
+    private static final int ID_DISPLAY_CUTOUT_TOP = createId(null, 1, displayCutout());
+    private static final int ID_DISPLAY_CUTOUT_RIGHT = createId(null, 2, displayCutout());
+    private static final int ID_DISPLAY_CUTOUT_BOTTOM = createId(null, 3, displayCutout());
+
     public final InsetsState mInsetsState;
 
     /**
@@ -97,28 +101,28 @@
         state.setDisplayShape(displayShape);
         state.getDisplayCutoutSafe(safe);
         if (safe.left > unrestricted.left) {
-            state.getSource(ITYPE_LEFT_DISPLAY_CUTOUT).setFrame(
+            state.getOrCreateSource(ID_DISPLAY_CUTOUT_LEFT, displayCutout()).setFrame(
                     unrestricted.left, unrestricted.top, safe.left, unrestricted.bottom);
         } else {
-            state.removeSource(ITYPE_LEFT_DISPLAY_CUTOUT);
+            state.removeSource(ID_DISPLAY_CUTOUT_LEFT);
         }
         if (safe.top > unrestricted.top) {
-            state.getSource(ITYPE_TOP_DISPLAY_CUTOUT).setFrame(
+            state.getOrCreateSource(ID_DISPLAY_CUTOUT_TOP, displayCutout()).setFrame(
                     unrestricted.left, unrestricted.top, unrestricted.right, safe.top);
         } else {
-            state.removeSource(ITYPE_TOP_DISPLAY_CUTOUT);
+            state.removeSource(ID_DISPLAY_CUTOUT_TOP);
         }
         if (safe.right < unrestricted.right) {
-            state.getSource(ITYPE_RIGHT_DISPLAY_CUTOUT).setFrame(
+            state.getOrCreateSource(ID_DISPLAY_CUTOUT_RIGHT, displayCutout()).setFrame(
                     safe.right, unrestricted.top, unrestricted.right, unrestricted.bottom);
         } else {
-            state.removeSource(ITYPE_RIGHT_DISPLAY_CUTOUT);
+            state.removeSource(ID_DISPLAY_CUTOUT_RIGHT);
         }
         if (safe.bottom < unrestricted.bottom) {
-            state.getSource(ITYPE_BOTTOM_DISPLAY_CUTOUT).setFrame(
+            state.getOrCreateSource(ID_DISPLAY_CUTOUT_BOTTOM, displayCutout()).setFrame(
                     unrestricted.left, safe.bottom, unrestricted.right, unrestricted.bottom);
         } else {
-            state.removeSource(ITYPE_BOTTOM_DISPLAY_CUTOUT);
+            state.removeSource(ID_DISPLAY_CUTOUT_BOTTOM);
         }
         return true;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index d759ff5..e87680a 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1741,21 +1741,6 @@
     }
 
     /**
-     * Get the Navigation Bar Frame height. This dimension is the height of the navigation bar that
-     * is used for spacing to show additional buttons on the navigation bar (such as the ime
-     * switcher when ime is visible).
-     *
-     * @param rotation specifies rotation to return dimension from
-     * @return navigation bar frame height
-     */
-    private int getNavigationBarFrameHeight(int rotation) {
-        if (mNavigationBar == null) {
-            return 0;
-        }
-        return mNavigationBar.mAttrs.forRotation(rotation).height;
-    }
-
-    /**
      * Return corner radius in pixels that should be used on windows in order to cover the display.
      *
      * The radius is only valid for internal displays, since the corner radius of external displays
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 85938e3..f38ae3f 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -17,7 +17,7 @@
 package com.android.server.wm;
 
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
 import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
@@ -57,7 +57,7 @@
     private Runnable mShowImeRunner;
     private boolean mIsImeLayoutDrawn;
     private boolean mImeShowing;
-    private final InsetsSource mLastSource = new InsetsSource(ITYPE_IME, WindowInsets.Type.ime());
+    private final InsetsSource mLastSource = new InsetsSource(ID_IME, WindowInsets.Type.ime());
 
     /** @see #setFrozen(boolean) */
     private boolean mFrozen;
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 1df534f..bd82113 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -25,7 +25,7 @@
 import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
 import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
 import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
@@ -315,12 +315,14 @@
         // The caller should not receive the visible insets provided by itself.
         if (attrs.type == TYPE_INPUT_METHOD) {
             state = new InsetsState(state);
-            state.removeSource(ITYPE_IME);
+            state.removeSource(ID_IME);
         } else if (attrs.providedInsets != null) {
             for (InsetsFrameProvider provider : attrs.providedInsets) {
                 // TODO(b/234093736): Let InsetsFrameProvider return the public type and the ID.
                 final int sourceId = provider.type;
-                final @InsetsType int type = InsetsState.toPublicType(sourceId);
+                final @InsetsType int type = sourceId == ID_IME
+                        ? WindowInsets.Type.ime()
+                        : InsetsState.toPublicType(sourceId);
                 if ((type & WindowInsets.Type.systemBars()) == 0) {
                     continue;
                 }
@@ -340,8 +342,7 @@
                 if (state == originalState) {
                     state = new InsetsState(state);
                 }
-                final InsetsSource override =
-                        new InsetsSource(state.getSource(otherProvider.getSource().getId()));
+                final InsetsSource override = new InsetsSource(otherProvider.getSource());
                 override.setFrame(otherProvider.getOverriddenFrame(windowType));
                 state.addSource(override);
             }
@@ -400,7 +401,7 @@
             // During switching tasks with gestural navigation, before the next IME input target
             // starts the input, we should adjust and freeze the last IME visibility of the window
             // in case delivering obsoleted IME insets state during transitioning.
-            final InsetsSource originalImeSource = originalState.peekSource(ITYPE_IME);
+            final InsetsSource originalImeSource = originalState.peekSource(ID_IME);
 
             if (originalImeSource != null) {
                 final boolean imeVisibility = w.isRequestedVisible(Type.ime());
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 49eaea2..f5af292 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_INSETS;
 import static com.android.server.wm.InsetsSourceProviderProto.CAPTURED_LEASH;
@@ -541,7 +541,7 @@
             return false;
         }
         for (int i = 0; i < providers.length; i++) {
-            if (providers[i].type == ITYPE_IME) {
+            if (providers[i].type == ID_IME) {
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 455cd48..a3f62b2 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -17,16 +17,19 @@
 package com.android.server.wm;
 
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.mandatorySystemGestures;
 import static android.view.WindowInsets.Type.systemGestures;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IME;
+import static com.android.server.wm.DisplayContentProto.IME_INSETS_SOURCE_PROVIDER;
+import static com.android.server.wm.DisplayContentProto.INSETS_SOURCE_PROVIDERS;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -34,7 +37,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.SparseArray;
-import android.view.InsetsSource;
+import android.util.proto.ProtoOutputStream;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
@@ -93,13 +96,11 @@
 
     InsetsStateController(DisplayContent displayContent) {
         mDisplayContent = displayContent;
-        mSourceProviderFunc = type -> {
-            final InsetsSource source = mState.getSource(type);
-            if (type == ITYPE_IME) {
-                return new ImeInsetsSourceProvider(source, this, mDisplayContent);
-            }
-            return new WindowContainerInsetsSourceProvider(source, this, mDisplayContent);
-        };
+        mSourceProviderFunc = id -> (id == ID_IME)
+                ? new ImeInsetsSourceProvider(mState.getOrCreateSource(
+                        id, ime()), this, mDisplayContent)
+                : new WindowContainerInsetsSourceProvider(mState.getOrCreateSource(
+                        id, InsetsState.toPublicType(id)), this, mDisplayContent);
     }
 
     InsetsState getRawInsetsState() {
@@ -124,14 +125,14 @@
     }
 
     /**
-     * @return The provider of a specific type.
+     * @return The provider of a specific source ID.
      */
-    WindowContainerInsetsSourceProvider getSourceProvider(@InternalInsetsType int type) {
-        return mProviders.computeIfAbsent(type, mSourceProviderFunc);
+    WindowContainerInsetsSourceProvider getSourceProvider(int id) {
+        return mProviders.computeIfAbsent(id, mSourceProviderFunc);
     }
 
     ImeInsetsSourceProvider getImeSourceProvider() {
-        return (ImeInsetsSourceProvider) getSourceProvider(ITYPE_IME);
+        return (ImeInsetsSourceProvider) getSourceProvider(ID_IME);
     }
 
     /**
@@ -216,7 +217,7 @@
         // Make sure that we always have a control target for the IME, even if the IME target is
         // null. Otherwise there is no leash that will hide it and IME becomes "randomly" visible.
         InsetsControlTarget target = imeTarget != null ? imeTarget : mEmptyImeControlTarget;
-        onControlChanged(ITYPE_IME, target);
+        onControlChanged(ID_IME, target);
         ProtoLog.d(WM_DEBUG_IME, "onImeControlTargetChanged %s",
                 target != null ? target.getWindow() : "null");
         notifyPendingInsetsControlChanged();
@@ -386,4 +387,15 @@
             mProviders.valueAt(i).dump(pw, prefix + "  ");
         }
     }
+
+    void dumpDebug(ProtoOutputStream proto, @WindowTraceLogLevel int logLevel) {
+        for (int i = mProviders.size() - 1; i >= 0; i--) {
+            final InsetsSourceProvider provider = mProviders.valueAt(i);
+            provider.dumpDebug(proto,
+                    provider.getSource().getType() == ime()
+                            ? IME_INSETS_SOURCE_PROVIDER
+                            : INSETS_SOURCE_PROVIDERS,
+                    logLevel);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 31f30af..703cb8a4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -225,7 +225,6 @@
 import android.view.InputWindowHandle;
 import android.view.InsetsSource;
 import android.view.InsetsState;
-import android.view.InsetsState.InternalInsetsType;
 import android.view.Surface;
 import android.view.Surface.Rotation;
 import android.view.SurfaceControl;
@@ -1531,6 +1530,10 @@
                 winAnimator.mDrawState = DRAW_PENDING;
                 if (mActivityRecord != null) {
                     mActivityRecord.clearAllDrawn();
+                    if (mAttrs.type == TYPE_APPLICATION_STARTING
+                            && mActivityRecord.mStartingData != null) {
+                        mActivityRecord.mStartingData.mIsDisplayed = false;
+                    }
                 }
             }
             if (!mWmService.mResizingWindows.contains(this)) {
@@ -1696,14 +1699,12 @@
      * Returns the insets state for the window and applies the requested visibility.
      */
     InsetsState getInsetsStateWithVisibilityOverride() {
-        final InsetsState state = new InsetsState(getInsetsState());
-        for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
-            final boolean requestedVisible = isRequestedVisible(InsetsState.toPublicType(type));
-            InsetsSource source = state.peekSource(type);
-            if (source != null && source.isVisible() != requestedVisible) {
-                source = new InsetsSource(source);
+        final InsetsState state = new InsetsState(getInsetsState(), true /* copySources */);
+        for (int i = state.sourceSize() - 1; i >= 0; i--) {
+            final InsetsSource source = state.sourceAt(i);
+            final boolean requestedVisible = isRequestedVisible(source.getType());
+            if (source.isVisible() != requestedVisible) {
                 source.setVisible(requestedVisible);
-                state.addSource(source);
             }
         }
         return state;
@@ -1882,8 +1883,8 @@
             return false;
         }
         for (int i = mProvidedInsetsSources.size() - 1; i >= 0; i--) {
-            final int type = mProvidedInsetsSources.keyAt(i);
-            if ((InsetsState.toPublicType(type) & WindowInsets.Type.navigationBars()) != 0) {
+            final InsetsSource source = mProvidedInsetsSources.valueAt(i);
+            if (source.getType() == WindowInsets.Type.navigationBars()) {
                 return true;
             }
         }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index e661688..e2bdcdd 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -62,9 +62,9 @@
         "com_android_server_tv_TvInputHal.cpp",
         "com_android_server_vr_VrManagerService.cpp",
         "com_android_server_UsbAlsaJackDetector.cpp",
+        "com_android_server_UsbAlsaMidiDevice.cpp",
         "com_android_server_UsbDeviceManager.cpp",
         "com_android_server_UsbDescriptorParser.cpp",
-        "com_android_server_UsbMidiDevice.cpp",
         "com_android_server_UsbHostManager.cpp",
         "com_android_server_vibrator_VibratorController.cpp",
         "com_android_server_vibrator_VibratorManagerService.cpp",
diff --git a/services/core/jni/com_android_server_UsbMidiDevice.cpp b/services/core/jni/com_android_server_UsbAlsaMidiDevice.cpp
similarity index 83%
rename from services/core/jni/com_android_server_UsbMidiDevice.cpp
rename to services/core/jni/com_android_server_UsbAlsaMidiDevice.cpp
index c8e7698..93938b1 100644
--- a/services/core/jni/com_android_server_UsbMidiDevice.cpp
+++ b/services/core/jni/com_android_server_UsbAlsaMidiDevice.cpp
@@ -14,27 +14,25 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "UsbMidiDeviceJNI"
+#define LOG_TAG "UsbAlsaMidiDeviceJNI"
 #define LOG_NDEBUG 0
-#include "utils/Log.h"
-
-#include "jni.h"
-#include <nativehelper/JNIPlatformHelp.h>
-#include <nativehelper/ScopedLocalRef.h>
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/Log.h"
-
 #include <asm/byteorder.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <nativehelper/JNIPlatformHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
 #include <sound/asound.h>
 #include <stdio.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
-namespace android
-{
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/Log.h"
+#include "jni.h"
+#include "utils/Log.h"
+
+namespace android {
 
 static jclass sFileDescriptorClass;
 static jfieldID sPipeFDField;
@@ -46,10 +44,10 @@
 // 1. Input O_RDONLY file descriptor
 // 2. Special input file descriptor to block the input thread
 // 3. Output O_WRONLY file descriptor
-static jobjectArray android_server_UsbMidiDevice_open(JNIEnv *env, jobject thiz, jint card,
-                                                      jint device, jint numInputs,
-                                                      jint numOutputs) {
-    char    path[100];
+static jobjectArray android_server_UsbAlsaMidiDevice_open(JNIEnv *env, jobject thiz, jint card,
+                                                          jint device, jint numInputs,
+                                                          jint numOutputs) {
+    char path[100];
     int fd;
 
     snprintf(path, sizeof(path), "/dev/snd/midiC%dD%d", card, device);
@@ -126,9 +124,7 @@
     return NULL;
 }
 
-static void
-android_server_UsbMidiDevice_close(JNIEnv *env, jobject thiz, jobjectArray fds)
-{
+static void android_server_UsbAlsaMidiDevice_close(JNIEnv *env, jobject thiz, jobjectArray fds) {
     // write to mPipeFD to unblock input thread
     jint pipeFD = env->GetIntField(thiz, sPipeFDField);
     write(pipeFD, &pipeFD, sizeof(pipeFD));
@@ -144,12 +140,12 @@
 
 static JNINativeMethod method_table[] = {
         {"nativeOpen", "(IIII)[Ljava/io/FileDescriptor;",
-         (void *)android_server_UsbMidiDevice_open},
-        {"nativeClose", "([Ljava/io/FileDescriptor;)V", (void *)android_server_UsbMidiDevice_close},
+         (void *)android_server_UsbAlsaMidiDevice_open},
+        {"nativeClose", "([Ljava/io/FileDescriptor;)V",
+         (void *)android_server_UsbAlsaMidiDevice_close},
 };
 
-int register_android_server_UsbMidiDevice(JNIEnv *env)
-{
+int register_android_server_UsbAlsaMidiDevice(JNIEnv *env) {
     jclass clazz = env->FindClass("java/io/FileDescriptor");
     if (clazz == NULL) {
         ALOGE("Can't find java/io/FileDescriptor");
@@ -157,19 +153,19 @@
     }
     sFileDescriptorClass = (jclass)env->NewGlobalRef(clazz);
 
-    clazz = env->FindClass("com/android/server/usb/UsbMidiDevice");
+    clazz = env->FindClass("com/android/server/usb/UsbAlsaMidiDevice");
     if (clazz == NULL) {
-        ALOGE("Can't find com/android/server/usb/UsbMidiDevice");
+        ALOGE("Can't find com/android/server/usb/UsbAlsaMidiDevice");
         return -1;
     }
     sPipeFDField = env->GetFieldID(clazz, "mPipeFD", "I");
     if (sPipeFDField == NULL) {
-        ALOGE("Can't find UsbMidiDevice.mPipeFD");
+        ALOGE("Can't find UsbAlsaMidiDevice.mPipeFD");
         return -1;
     }
 
-    return jniRegisterNativeMethods(env, "com/android/server/usb/UsbMidiDevice",
-            method_table, NELEM(method_table));
+    return jniRegisterNativeMethods(env, "com/android/server/usb/UsbAlsaMidiDevice", method_table,
+                                    NELEM(method_table));
 }
 
-};
+}; // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 00f851f..290ad8d 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -35,8 +35,8 @@
 int register_android_server_SerialService(JNIEnv* env);
 int register_android_server_SystemServer(JNIEnv* env);
 int register_android_server_UsbAlsaJackDetector(JNIEnv* env);
+int register_android_server_UsbAlsaMidiDevice(JNIEnv* env);
 int register_android_server_UsbDeviceManager(JNIEnv* env);
-int register_android_server_UsbMidiDevice(JNIEnv* env);
 int register_android_server_UsbHostManager(JNIEnv* env);
 int register_android_server_vr_VrManagerService(JNIEnv* env);
 int register_android_server_vibrator_VibratorController(JavaVM* vm, JNIEnv* env);
@@ -90,8 +90,8 @@
     register_android_server_InputManager(env);
     register_android_server_LightsService(env);
     register_android_server_UsbDeviceManager(env);
-    register_android_server_UsbMidiDevice(env);
     register_android_server_UsbAlsaJackDetector(env);
+    register_android_server_UsbAlsaMidiDevice(env);
     register_android_server_UsbHostManager(env);
     register_android_server_vr_VrManagerService(env);
     register_android_server_vibrator_VibratorController(vm, env);
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index d9a6d54..ff72ed7 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -29,8 +29,8 @@
 import android.credentials.CreateCredentialException;
 import android.credentials.CreateCredentialRequest;
 import android.credentials.CredentialDescription;
+import android.credentials.CredentialOption;
 import android.credentials.GetCredentialException;
-import android.credentials.GetCredentialOption;
 import android.credentials.GetCredentialRequest;
 import android.credentials.IClearCredentialStateCallback;
 import android.credentials.ICreateCredentialCallback;
@@ -268,10 +268,10 @@
 
         // All requested credential descriptions based on the given request.
         Set<String> requestedCredentialDescriptions =
-                request.getGetCredentialOptions().stream().map(
-                        getCredentialOption -> getCredentialOption
+                request.getCredentialOptions().stream().map(
+                        credentialOption -> credentialOption
                                         .getCredentialRetrievalData()
-                                        .getString(GetCredentialOption
+                                        .getString(CredentialOption
                                                 .FLATTENED_REQUEST))
                         .collect(Collectors.toSet());
 
@@ -342,11 +342,11 @@
 
             // Initiate all provider sessions
             List<ProviderSession> providerSessions =
-                        initiateProviderSessions(
-                                session,
-                                request.getGetCredentialOptions().stream()
-                                        .map(GetCredentialOption::getType)
-                                        .collect(Collectors.toList()));
+                    initiateProviderSessions(
+                            session,
+                            request.getCredentialOptions().stream()
+                                    .map(CredentialOption::getType)
+                                    .collect(Collectors.toList()));
 
             if (providerSessions.isEmpty()) {
                 try {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index b4058ed..dec3432 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -21,8 +21,8 @@
 import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.Intent;
+import android.credentials.CredentialOption;
 import android.credentials.GetCredentialException;
-import android.credentials.GetCredentialOption;
 import android.credentials.GetCredentialResponse;
 import android.credentials.ui.Entry;
 import android.credentials.ui.GetCredentialProviderData;
@@ -107,7 +107,7 @@
     ) {
         return new BeginGetCredentialRequest.Builder(callingAppInfo)
                 .setBeginGetCredentialOptions(
-                        filteredRequest.getGetCredentialOptions().stream().map(
+                        filteredRequest.getCredentialOptions().stream().map(
                                 option -> {
                                     return new BeginGetCredentialOption(
                                             option.getType(),
@@ -121,8 +121,8 @@
             List<String> providerCapabilities,
             android.credentials.GetCredentialRequest clientRequest
     ) {
-        List<GetCredentialOption> filteredOptions = new ArrayList<>();
-        for (GetCredentialOption option : clientRequest.getGetCredentialOptions()) {
+        List<CredentialOption> filteredOptions = new ArrayList<>();
+        for (CredentialOption option : clientRequest.getCredentialOptions()) {
             if (providerCapabilities.contains(option.getType())) {
                 Log.i(TAG, "In createProviderRequest - capability found : "
                         + option.getType());
@@ -135,7 +135,7 @@
         if (!filteredOptions.isEmpty()) {
             return new android.credentials.GetCredentialRequest
                     .Builder(clientRequest.getData())
-                    .setGetCredentialOptions(
+                    .setCredentialOptions(
                             filteredOptions).build();
         }
         Log.i(TAG, "In createProviderRequest - returning null");
@@ -307,7 +307,7 @@
 
     private Intent setUpFillInIntent(String type) {
         Intent intent = new Intent();
-        for (GetCredentialOption option : mCompleteRequest.getGetCredentialOptions()) {
+        for (CredentialOption option : mCompleteRequest.getCredentialOptions()) {
             if (option.getType().equals(type)) {
                 intent.putExtra(
                         CredentialProviderService
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c67ffd5..c6b2207 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -18435,9 +18435,9 @@
                         .addExtras(extras)
                         .build();
 
-        mInjector.getNotificationManager().notifyAsUser(
+        mHandler.post(() -> mInjector.getNotificationManager().notifyAsUser(
                 null, SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED, notification,
-                UserHandle.of(getProfileParentId(profileUserId)));
+                UserHandle.of(getProfileParentId(profileUserId))));
     }
 
     private String getPersonalAppSuspensionButtonText() {
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index f2cff62..a26b2ac 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -71,6 +71,10 @@
         return opNameMapToOpIntMap(getUidModes(uid))
     }
 
+    override fun getNonDefaultPackageModes(packageName: String, userId: Int): SparseIntArray {
+        return opNameMapToOpIntMap(getPackageModes(packageName, userId))
+    }
+
     override fun getUidMode(uid: Int, op: Int): Int {
         val appId = UserHandle.getAppId(uid)
         val userId = UserHandle.getUserId(uid)
diff --git a/services/proguard.flags b/services/proguard.flags
index ba4560f..c133044 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -103,7 +103,7 @@
 -keep,allowoptimization,allowaccessmodification class com.android.server.storage.AppFuseBridge { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.tv.TvInputHal { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbAlsaJackDetector { *; }
--keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbMidiDevice { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbAlsaMidiDevice { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorController$OnVibrationCompleteListener { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorManagerService$OnSyncedVibrationCompleteListener { *; }
 -keepclasseswithmembers,allowoptimization,allowaccessmodification class com.android.server.** {
diff --git a/services/searchui/java/com/android/server/searchui/SearchUiManagerService.java b/services/searchui/java/com/android/server/searchui/SearchUiManagerService.java
index 524002a..29ad537 100644
--- a/services/searchui/java/com/android/server/searchui/SearchUiManagerService.java
+++ b/services/searchui/java/com/android/server/searchui/SearchUiManagerService.java
@@ -125,6 +125,26 @@
                     (service) -> service.queryLocked(sessionId, query, callback));
         }
 
+        public void registerEmptyQueryResultUpdateCallback(@NonNull SearchSessionId sessionId,
+                @NonNull ISearchCallback callback) {
+            runForUserLocked("registerEmptyQueryResultUpdateCallback", sessionId,
+                    (service) -> service.registerEmptyQueryResultUpdateCallbackLocked(sessionId,
+                            callback));
+        }
+
+        public void unregisterEmptyQueryResultUpdateCallback(@NonNull SearchSessionId sessionId,
+                @NonNull ISearchCallback callback) {
+            runForUserLocked("unregisterEmptyQueryResultUpdateCallback", sessionId,
+                    (service) -> service.unregisterEmptyQueryResultUpdateCallbackLocked(sessionId,
+                            callback));
+        }
+
+        @Override
+        public void requestEmptyQueryResultUpdate(@NonNull SearchSessionId sessionId) {
+            runForUserLocked("requestEmptyQueryResultUpdate", sessionId,
+                    (service) -> service.requestEmptyQueryResultUpdateLocked(sessionId));
+        }
+
         @Override
         public void destroySearchSession(@NonNull SearchSessionId sessionId) {
             runForUserLocked("destroySearchSession", sessionId,
diff --git a/services/searchui/java/com/android/server/searchui/SearchUiPerUserService.java b/services/searchui/java/com/android/server/searchui/SearchUiPerUserService.java
index 4c31978..0d70fff 100644
--- a/services/searchui/java/com/android/server/searchui/SearchUiPerUserService.java
+++ b/services/searchui/java/com/android/server/searchui/SearchUiPerUserService.java
@@ -149,6 +149,48 @@
     }
 
     /**
+     * Registers a callback for continuous updates of search targets for empty query result used for
+     * zero state.
+     */
+    @GuardedBy("mLock")
+    public void registerEmptyQueryResultUpdateCallbackLocked(@NonNull SearchSessionId sessionId,
+            @NonNull ISearchCallback callback) {
+        final SearchSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        final boolean serviceExists = resolveService(sessionId,
+                s -> s.onRegisterEmptyQueryResultUpdateCallback(sessionId, callback));
+        if (serviceExists) {
+            sessionInfo.addCallbackLocked(callback);
+        }
+    }
+
+    /**
+     * Unregisters a callback for continuous updates of search targets for empty query result
+     * used for zero state.
+     */
+    @GuardedBy("mLock")
+    public void unregisterEmptyQueryResultUpdateCallbackLocked(@NonNull SearchSessionId sessionId,
+            @NonNull ISearchCallback callback) {
+        final SearchSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        final boolean serviceExists = resolveService(sessionId,
+                s -> s.onUnregisterEmptyQueryResultUpdateCallback(sessionId, callback));
+        if (serviceExists) {
+            sessionInfo.removeCallbackLocked(callback);
+        }
+    }
+
+    /**
+     * Requests a new set of search targets for empty query result used for zero state.
+     */
+    @GuardedBy("mLock")
+    public void requestEmptyQueryResultUpdateLocked(@NonNull SearchSessionId sessionId) {
+        final SearchSessionInfo sessionInfo = mSessionInfos.get(sessionId);
+        if (sessionInfo == null) return;
+        resolveService(sessionId, s->s.onRequestEmptyQueryResultUpdate(sessionId));
+    }
+
+    /**
      * Notifies the service of the end of an existing search session.
      */
     @GuardedBy("mLock")
@@ -310,18 +352,7 @@
         final IBinder.DeathRecipient mDeathRecipient;
 
         private final RemoteCallbackList<ISearchCallback> mCallbacks =
-                new RemoteCallbackList<ISearchCallback>() {
-                    @Override
-                    public void onCallbackDied(ISearchCallback callback) {
-                        if (DEBUG) {
-                            Slog.d(TAG, "Binder died for session Id=" + mSessionId
-                                    + " and callback=" + callback.asBinder());
-                        }
-                        if (mCallbacks.getRegisteredCallbackCount() == 0) {
-                            destroy();
-                        }
-                    }
-                };
+                new RemoteCallbackList<>();
 
         SearchSessionInfo(
                 @NonNull final SearchSessionId id,
@@ -337,6 +368,22 @@
             mDeathRecipient = deathRecipient;
         }
 
+        void addCallbackLocked(ISearchCallback callback) {
+            if (DEBUG) {
+                Slog.d(TAG, "Storing callback for session Id=" + mSessionId
+                        + " and callback=" + callback.asBinder());
+            }
+            mCallbacks.register(callback);
+        }
+
+        void removeCallbackLocked(ISearchCallback callback) {
+            if (DEBUG) {
+                Slog.d(TAG, "Removing callback for session Id=" + mSessionId
+                        + " and callback=" + callback.asBinder());
+            }
+            mCallbacks.unregister(callback);
+        }
+
         boolean linkToDeath() {
             try {
                 mToken.linkToDeath(mDeathRecipient, 0);
@@ -369,6 +416,9 @@
                         + callbackCount + " callbacks.");
             }
             service.onCreateSearchSessionLocked(mSearchContext, mSessionId, token);
+            mCallbacks.broadcast(
+                    callback -> service.registerEmptyQueryResultUpdateCallbackLocked(mSessionId,
+                            callback));
         }
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index 9eed6ad..d1f7f93 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -42,6 +42,7 @@
 import android.content.res.AssetManager;
 import android.os.Handler;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -51,12 +52,14 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.util.ArrayUtils;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.server.LocalServices;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.pm.pkg.PackageStateInternal;
 
 import org.junit.After;
 import org.junit.Before;
@@ -188,6 +191,11 @@
 
         // Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat
         doReturn(null).when(mPackageManager).getPackagesForUid(anyInt());
+
+        doReturn(new ArrayMap<String, PackageStateInternal>()).when(mPackageManagerInternal)
+                .getPackageStates();
+
+        doReturn(new int[] {0}).when(mUserManagerInternal).getUserIds();
     }
 
     @After
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index ed78e72..c7bacbc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -32,6 +32,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
@@ -470,6 +472,50 @@
     }
 
     /**
+     * Confirm that
+     * returns {@code null} when for user-visible jobs stopped by the user.
+     */
+    @Test
+    public void testGetRescheduleJobForFailure_userStopped() {
+        JobStatus uiJob = createJobStatus("testGetRescheduleJobForFailure",
+                createJobInfo().setUserInitiated(true));
+        JobStatus uvJob = createJobStatus("testGetRescheduleJobForFailure", createJobInfo());
+        spyOn(uvJob);
+        doReturn(true).when(uvJob).isUserVisibleJob();
+        JobStatus regJob = createJobStatus("testGetRescheduleJobForFailure", createJobInfo());
+
+        // Reschedule for a non-user reason
+        JobStatus rescheduledUiJob = mService.getRescheduleJobForFailureLocked(uiJob,
+                JobParameters.STOP_REASON_DEVICE_STATE,
+                JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
+        JobStatus rescheduledUvJob = mService.getRescheduleJobForFailureLocked(uvJob,
+                JobParameters.STOP_REASON_DEVICE_STATE,
+                JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
+        JobStatus rescheduledRegJob = mService.getRescheduleJobForFailureLocked(regJob,
+                JobParameters.STOP_REASON_DEVICE_STATE,
+                JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
+        assertNotNull(rescheduledUiJob);
+        assertNotNull(rescheduledUvJob);
+        assertNotNull(rescheduledRegJob);
+
+        // Reschedule for a user reason. The user-visible jobs shouldn't be rescheduled.
+        spyOn(rescheduledUvJob);
+        doReturn(true).when(rescheduledUvJob).isUserVisibleJob();
+        rescheduledUiJob = mService.getRescheduleJobForFailureLocked(rescheduledUiJob,
+                JobParameters.STOP_REASON_USER,
+                JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
+        rescheduledUvJob = mService.getRescheduleJobForFailureLocked(rescheduledUvJob,
+                JobParameters.STOP_REASON_USER,
+                JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
+        rescheduledRegJob = mService.getRescheduleJobForFailureLocked(rescheduledRegJob,
+                JobParameters.STOP_REASON_USER,
+                JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
+        assertNull(rescheduledUiJob);
+        assertNull(rescheduledUvJob);
+        assertNotNull(rescheduledRegJob);
+    }
+
+    /**
      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
      * with the correct delay and deadline constraints if the periodic job is scheduled with the
      * minimum possible period.
@@ -1274,14 +1320,14 @@
         mService.getJobStore().add(job2a);
         mService.getJobStore().add(job2b);
 
-        mService.stopUserVisibleJobsInternal("pkg1", 1);
+        mService.notePendingUserRequestedAppStopInternal("pkg1", 1, "test");
         assertEquals(4, mService.getPendingJobQueue().size());
         assertTrue(mService.getPendingJobQueue().contains(job1a));
         assertTrue(mService.getPendingJobQueue().contains(job1b));
         assertTrue(mService.getPendingJobQueue().contains(job2a));
         assertTrue(mService.getPendingJobQueue().contains(job2b));
 
-        mService.stopUserVisibleJobsInternal("pkg1", 0);
+        mService.notePendingUserRequestedAppStopInternal("pkg1", 0, "test");
         assertEquals(2, mService.getPendingJobQueue().size());
         assertFalse(mService.getPendingJobQueue().contains(job1a));
         assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job1a));
@@ -1290,7 +1336,7 @@
         assertTrue(mService.getPendingJobQueue().contains(job2a));
         assertTrue(mService.getPendingJobQueue().contains(job2b));
 
-        mService.stopUserVisibleJobsInternal("pkg2", 0);
+        mService.notePendingUserRequestedAppStopInternal("pkg2", 0, "test");
         assertEquals(0, mService.getPendingJobQueue().size());
         assertFalse(mService.getPendingJobQueue().contains(job1a));
         assertFalse(mService.getPendingJobQueue().contains(job1b));
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 2c47fd9..e6bc72f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -225,6 +225,33 @@
     }
 
     @Test
+    public void testIsUserVisibleJob() {
+        JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+                .setUserInitiated(false)
+                .build();
+        JobStatus job = createJobStatus(jobInfo);
+
+        assertFalse(job.isUserVisibleJob());
+
+        // User-initiated jobs are always user-visible unless they've been demoted.
+        jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
+                .setUserInitiated(true)
+                .build();
+        job = createJobStatus(jobInfo);
+
+        assertTrue(job.isUserVisibleJob());
+
+        job.addInternalFlags(JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
+        assertFalse(job.isUserVisibleJob());
+
+        job.startedAsUserInitiatedJob = true;
+        assertTrue(job.isUserVisibleJob());
+
+        job.startedAsUserInitiatedJob = false;
+        assertFalse(job.isUserVisibleJob());
+    }
+
+    @Test
     public void testMediaBackupExemption_lateConstraint() {
         final JobInfo triggerContentJob = new JobInfo.Builder(42, TEST_JOB_COMPONENT)
                 .addTriggerContentUri(new JobInfo.TriggerContentUri(IMAGES_MEDIA_URI, 0))
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 9a43762..1904671 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -56,6 +56,7 @@
 import android.hardware.display.DisplayViewport;
 import android.hardware.display.DisplayedContentSample;
 import android.hardware.display.DisplayedContentSamplingAttributes;
+import android.hardware.display.HdrConversionMode;
 import android.hardware.display.IDisplayManagerCallback;
 import android.hardware.display.IVirtualDisplayCallback;
 import android.hardware.display.VirtualDisplayConfig;
@@ -171,6 +172,17 @@
                }
            });
        }
+
+        @Override
+        void setHdrConversionMode(int conversionMode, int preferredHdrOutputType,
+                int[] autoHdrTypes) {
+            return;
+        }
+
+        @Override
+        int[] getSupportedHdrOutputTypes() {
+            return new int[]{};
+        }
    }
 
     private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
@@ -240,8 +252,7 @@
         // the usage of SensorManager, which is available only after the PowerManagerService
         // is ready.
         resetConfigToIgnoreSensorManager(mContext);
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         registerDefaultDisplays(displayManager);
         displayManager.systemReady(false /* safeMode */);
         displayManager.windowManagerAndInputReady();
@@ -316,8 +327,7 @@
         // the usage of SensorManager, which is available only after the PowerManagerService
         // is ready.
         resetConfigToIgnoreSensorManager(mContext);
-        DisplayManagerService displayManager =
-                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         registerDefaultDisplays(displayManager);
         displayManager.systemReady(false /* safeMode */);
         displayManager.windowManagerAndInputReady();
@@ -1511,6 +1521,22 @@
 
     }
 
+    @Test
+    public void testHdrConversionModeEquals() {
+        assertEquals(
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2));
+        assertNotEquals(
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 3));
+        assertEquals(
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM),
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+        assertNotEquals(
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_FORCE, 2),
+                new HdrConversionMode(HdrConversionMode.HDR_CONVERSION_SYSTEM));
+    }
+
     private void testDisplayInfoFrameRateOverrideModeCompat(boolean compatChangeEnabled)
             throws Exception {
         DisplayManagerService displayManager =
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index e663245..56d59b4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -48,7 +48,7 @@
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.Process.NOBODY_UID;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
@@ -3177,18 +3177,18 @@
     public void testImeInsetsFrozenFlag_resetWhenReportedToBeImeInputTarget() {
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
 
-        mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindowContainer(
+        mDisplayContent.getInsetsStateController().getSourceProvider(ID_IME).setWindowContainer(
                 mImeWindow, null, null);
         mImeWindow.getControllableInsetProvider().setServerVisible(true);
 
-        InsetsSource imeSource = new InsetsSource(ITYPE_IME, ime());
+        InsetsSource imeSource = new InsetsSource(ID_IME, ime());
         app.mAboveInsetsState.addSource(imeSource);
         mDisplayContent.setImeLayeringTarget(app);
         mDisplayContent.updateImeInputAndControlTarget(app);
 
         InsetsState state = app.getInsetsState();
-        assertFalse(state.getSource(imeSource.getId()).isVisible());
-        assertTrue(state.getSource(imeSource.getId()).getFrame().isEmpty());
+        assertFalse(state.getOrCreateSource(imeSource.getId(), ime()).isVisible());
+        assertTrue(state.getOrCreateSource(imeSource.getId(), ime()).getFrame().isEmpty());
 
         // Simulate app is closing and expect IME insets is frozen.
         mDisplayContent.mOpeningApps.clear();
@@ -3211,8 +3211,8 @@
         // Verify when IME is visible and the app can receive the right IME insets from policy.
         makeWindowVisibleAndDrawn(app, mImeWindow);
         state = app.getInsetsState();
-        assertTrue(state.getSource(ITYPE_IME).isVisible());
-        assertEquals(state.getSource(ITYPE_IME).getFrame(), imeSource.getFrame());
+        assertTrue(state.peekSource(ID_IME).isVisible());
+        assertEquals(state.peekSource(ID_IME).getFrame(), imeSource.getFrame());
     }
 
     @SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@@ -3222,7 +3222,7 @@
         final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1");
         final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
 
-        mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindowContainer(
+        mDisplayContent.getInsetsStateController().getSourceProvider(ID_IME).setWindowContainer(
                 mImeWindow, null, null);
         mImeWindow.getControllableInsetProvider().setServerVisible(true);
 
@@ -3237,7 +3237,7 @@
         mDisplayContent.getInsetsStateController().onInsetsModified(app1);
 
         // Verify app1's IME insets is visible and app2's IME insets frozen flag set.
-        assertTrue(app1.getInsetsState().peekSource(ITYPE_IME).isVisible());
+        assertTrue(app1.getInsetsState().peekSource(ID_IME).isVisible());
         assertTrue(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput);
 
         // Simulate switching to app2 to make it visible to be IME targets.
@@ -3256,7 +3256,7 @@
         verify(app2.mClient, atLeastOnce()).resized(any(), anyBoolean(), any(),
                 insetsStateCaptor.capture(), anyBoolean(), anyBoolean(), anyInt(), anyInt(),
                 anyBoolean());
-        assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
+        assertFalse(app2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
     }
 
     @Test
@@ -3288,7 +3288,7 @@
         makeWindowVisibleAndDrawn(app1, app2);
 
         final InsetsStateController controller = mDisplayContent.getInsetsStateController();
-        controller.getSourceProvider(ITYPE_IME).setWindowContainer(
+        controller.getSourceProvider(ID_IME).setWindowContainer(
                 ime, null, null);
         ime.getControllableInsetProvider().setServerVisible(true);
 
@@ -3306,8 +3306,8 @@
         controller.onInsetsModified(app1);
 
         // Expect all activities in split-screen will get IME insets visible state
-        assertTrue(app1.getInsetsState().peekSource(ITYPE_IME).isVisible());
-        assertTrue(app2.getInsetsState().peekSource(ITYPE_IME).isVisible());
+        assertTrue(app1.getInsetsState().peekSource(ID_IME).isVisible());
+        assertTrue(app2.getInsetsState().peekSource(ID_IME).isVisible());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 3b34ba4..abc0c14 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1557,13 +1557,13 @@
         // If the visibility of insets state is changed, the rotated state should be updated too.
         final InsetsState rotatedState = app.getFixedRotationTransformInsetsState();
         final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
-        assertEquals(state.getSource(ITYPE_STATUS_BAR).isVisible(),
-                rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
-        state.getSource(ITYPE_STATUS_BAR).setVisible(
-                !rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
+        assertEquals(state.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()),
+                rotatedState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
+        state.setSourceVisible(ITYPE_STATUS_BAR,
+                !rotatedState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
         mDisplayContent.getInsetsStateController().notifyInsetsChanged();
-        assertEquals(state.getSource(ITYPE_STATUS_BAR).isVisible(),
-                rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
+        assertEquals(state.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()),
+                rotatedState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
 
         final Rect outFrame = new Rect();
         final Rect outInsets = new Rect();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 6733470..45cf530 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -43,6 +43,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.DisplayInfo;
 import android.view.InsetsFrameProvider;
+import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.PrivacyIndicatorBounds;
 import android.view.RoundedCorners;
@@ -255,7 +256,7 @@
                 mDisplayContent.getInsetsStateController().getRawInsetsState());
         // Exclude comparing IME insets because currently the simulated layout only focuses on the
         // insets from status bar and navigation bar.
-        realInsetsState.removeSource(InsetsState.ITYPE_IME);
+        realInsetsState.removeSource(InsetsSource.ID_IME);
         realInsetsState.removeSource(InsetsState.ITYPE_CAPTION_BAR);
 
         assertEquals(new ToStringComparatorWrapper<>(realInsetsState),
@@ -270,9 +271,9 @@
                 .rotationForActivityInDifferentOrientation(eq(mWindow.mActivityRecord));
         mWindow.mAboveInsetsState.set(
                 mDisplayContent.getInsetsStateController().getRawInsetsState());
-        final Rect frame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame();
+        final Rect frame = mWindow.getInsetsState().peekSource(ITYPE_STATUS_BAR).getFrame();
         mDisplayContent.rotateInDifferentOrientationIfNeeded(mWindow.mActivityRecord);
-        final Rect rotatedFrame = mWindow.getInsetsState().getSource(ITYPE_STATUS_BAR).getFrame();
+        final Rect rotatedFrame = mWindow.getInsetsState().peekSource(ITYPE_STATUS_BAR).getFrame();
 
         assertEquals(DISPLAY_WIDTH, frame.width());
         assertEquals(DISPLAY_HEIGHT, rotatedFrame.width());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index cf9ec81..6bb7769 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -17,8 +17,8 @@
 package com.android.server.wm;
 
 import static android.view.DisplayCutout.NO_CUTOUT;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.RoundedCorners.NO_ROUNDED_CORNERS;
 import static android.view.Surface.ROTATION_0;
@@ -335,7 +335,7 @@
         displayPolicy.layoutWindowLw(mNavBarWindow, null, mDisplayContent.mDisplayFrames);
         displayPolicy.layoutWindowLw(mImeWindow, null, mDisplayContent.mDisplayFrames);
 
-        final InsetsSource imeSource = state.peekSource(ITYPE_IME);
+        final InsetsSource imeSource = state.peekSource(ID_IME);
         final InsetsSource navBarSource = state.peekSource(ITYPE_NAVIGATION_BAR);
 
         assertNotNull(imeSource);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index d4e860e..20bb549 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -39,7 +39,7 @@
 @RunWith(WindowTestRunner.class)
 public class ImeInsetsSourceProviderTest extends WindowTestsBase {
 
-    private InsetsSource mImeSource = new InsetsSource(ITYPE_IME, ime());
+    private InsetsSource mImeSource = new InsetsSource(ID_IME, ime());
     private ImeInsetsSourceProvider mImeProvider;
 
     @Before
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 9887839..dba2995 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -276,9 +276,9 @@
         policy.updateBarControlTarget(mAppWindow);
         waitUntilWindowAnimatorIdle();
         assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState()
-                .getSource(ITYPE_STATUS_BAR).isVisible());
+                .isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
         assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState()
-                .getSource(ITYPE_NAVIGATION_BAR).isVisible());
+                .isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars()));
 
         policy.showTransient(new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR},
                 true /* isGestureOnSystemBar */);
@@ -293,9 +293,9 @@
         }
 
         assertTrue(mDisplayContent.getInsetsStateController().getRawInsetsState()
-                .getSource(ITYPE_STATUS_BAR).isVisible());
+                .isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
         assertTrue(mDisplayContent.getInsetsStateController().getRawInsetsState()
-                .getSource(ITYPE_NAVIGATION_BAR).isVisible());
+                .isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars()));
     }
 
     @SetupWindows(addWindows = W_ACTIVITY)
@@ -362,11 +362,11 @@
 
         final InsetsState clientState = mAppWindow.getInsetsState();
         // The transient bar states for client should be invisible.
-        assertFalse(clientState.getSource(ITYPE_STATUS_BAR).isVisible());
-        assertFalse(clientState.getSource(ITYPE_NAVIGATION_BAR).isVisible());
+        assertFalse(clientState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
+        assertFalse(clientState.isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars()));
         // The original state shouldn't be modified.
-        assertTrue(state.getSource(ITYPE_STATUS_BAR).isVisible());
-        assertTrue(state.getSource(ITYPE_NAVIGATION_BAR).isVisible());
+        assertTrue(state.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
+        assertTrue(state.isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars()));
 
         mAppWindow.setRequestedVisibleTypes(
                 navigationBars() | statusBars(), navigationBars() | statusBars());
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 4d69979..88ecd3f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -19,12 +19,13 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -76,9 +77,9 @@
                 null);
         getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindowContainer(navBar, null,
                 null);
-        getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null);
+        getController().getSourceProvider(ID_IME).setWindowContainer(ime, null, null);
 
-        assertNull(navBar.getInsetsState().peekSource(ITYPE_IME));
+        assertNull(navBar.getInsetsState().peekSource(ID_IME));
         assertNull(navBar.getInsetsState().peekSource(ITYPE_STATUS_BAR));
     }
 
@@ -96,7 +97,7 @@
 
         assertNull(app.getInsetsState().peekSource(ITYPE_STATUS_BAR));
         assertNull(app.getInsetsState().peekSource(ITYPE_NAVIGATION_BAR));
-        assertNull(app.getInsetsState().peekSource(ITYPE_IME));
+        assertNull(app.getInsetsState().peekSource(ID_IME));
     }
 
     @Test
@@ -135,43 +136,41 @@
     @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testStripForDispatch_independentSources() {
-        getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
+        getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
 
         final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1");
         final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
 
-        app1.mAboveInsetsState.addSource(getController().getRawInsetsState().getSource(ITYPE_IME));
+        app1.mAboveInsetsState.addSource(getController().getRawInsetsState().peekSource(ID_IME));
 
-        getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
-        assertFalse(app2.getInsetsState().getSource(ITYPE_IME)
-                .isVisible());
-        assertTrue(app1.getInsetsState().getSource(ITYPE_IME)
-                .isVisible());
+        getController().getRawInsetsState().setSourceVisible(ID_IME, true);
+        assertFalse(app2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertTrue(app1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
     }
 
     @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testStripForDispatch_belowIme() {
-        getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
+        getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
 
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
-        app.mAboveInsetsState.getSource(ITYPE_IME).setVisible(true);
-        app.mAboveInsetsState.getSource(ITYPE_IME).setFrame(mImeWindow.getFrame());
+        app.mAboveInsetsState.getOrCreateSource(ID_IME, ime())
+                .setVisible(true)
+                .setFrame(mImeWindow.getFrame());
 
-        getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
-        assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
+        getController().getRawInsetsState().setSourceVisible(ID_IME, true);
+        assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
     }
 
     @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testStripForDispatch_aboveIme() {
-        getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
+        getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
 
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
 
-        getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
-        assertFalse(app.getInsetsState().getSource(ITYPE_IME)
-                .isVisible());
+        getController().getRawInsetsState().setSourceVisible(ID_IME, true);
+        assertFalse(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
     }
 
     @SetupWindows(addWindows = W_INPUT_METHOD)
@@ -185,7 +184,7 @@
 
         // Make IME and stay visible during the test.
         mImeWindow.setHasSurface(true);
-        getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
+        getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
         getController().onImeControlTargetChanged(
                 mDisplayContent.getImeInputTarget().getWindowState());
         mDisplayContent.getImeInputTarget().getWindowState().setRequestedVisibleTypes(ime(), ime());
@@ -205,9 +204,8 @@
         mDisplayContent.applySurfaceChangesTransaction();
 
         // app won't get visible IME insets while above IME even when IME is visible.
-        assertTrue(getController().getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
-        assertFalse(app.getInsetsState().getSource(ITYPE_IME)
-                .isVisible());
+        assertTrue(getController().getRawInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertFalse(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
 
         // Reset invocation counter.
         clearInvocations(app);
@@ -216,20 +214,21 @@
         app.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE;
         mDisplayContent.computeImeTarget(true);
         mDisplayContent.applySurfaceChangesTransaction();
-        app.mAboveInsetsState.getSource(ITYPE_IME).setVisible(true);
-        app.mAboveInsetsState.getSource(ITYPE_IME).setFrame(mImeWindow.getFrame());
+        app.mAboveInsetsState.getOrCreateSource(ID_IME, ime())
+                .setVisible(true)
+                .setFrame(mImeWindow.getFrame());
 
         // Make sure app got notified.
         verify(app, atLeastOnce()).notifyInsetsChanged();
 
         // app will get visible IME insets while below IME.
-        assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
+        assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
     }
 
     @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testStripForDispatch_childWindow_altFocusable() {
-        getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
+        getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
 
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         final WindowState child = createWindow(app, TYPE_APPLICATION, "child");
@@ -241,20 +240,19 @@
         mDisplayContent.setLayoutNeeded();
         mDisplayContent.applySurfaceChangesTransaction();
 
-        getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
-        assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
-        assertFalse(child.getInsetsState().getSource(ITYPE_IME)
-                .isVisible());
+        getController().getRawInsetsState().setSourceVisible(ID_IME, true);
+        assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertFalse(child.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
     }
 
     @SetupWindows(addWindows = W_INPUT_METHOD)
     @Test
     public void testStripForDispatch_childWindow_splitScreen() {
-        getController().getSourceProvider(ITYPE_IME).setWindowContainer(mImeWindow, null, null);
+        getController().getSourceProvider(ID_IME).setWindowContainer(mImeWindow, null, null);
 
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         final WindowState child = createWindow(app, TYPE_APPLICATION, "child");
-        app.mAboveInsetsState.addSource(getController().getRawInsetsState().peekSource(ITYPE_IME));
+        app.mAboveInsetsState.addSource(getController().getRawInsetsState().peekSource(ID_IME));
         child.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
         child.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
 
@@ -262,10 +260,9 @@
         mDisplayContent.setLayoutNeeded();
         mDisplayContent.applySurfaceChangesTransaction();
 
-        getController().getRawInsetsState().setSourceVisible(ITYPE_IME, true);
-        assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
-        assertFalse(child.getInsetsState().getSource(ITYPE_IME)
-                .isVisible());
+        getController().getRawInsetsState().setSourceVisible(ID_IME, true);
+        assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertFalse(child.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
     }
 
     @Test
@@ -283,14 +280,14 @@
         imeOverrideProviders.put(TYPE_INPUT_METHOD, ((displayFrames, windowState, rect) ->
                 rect.set(0, 1, 2, 3)));
         statusBarProvider.setWindowContainer(statusBar, null, imeOverrideProviders);
-        getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null);
+        getController().getSourceProvider(ID_IME).setWindowContainer(ime, null, null);
         statusBar.setControllableInsetProvider(statusBarProvider);
         statusBar.updateSourceFrame(statusBar.getFrame());
 
         statusBarProvider.onPostLayout();
 
         final InsetsState state = ime.getInsetsState();
-        assertEquals(new Rect(0, 1, 2, 3), state.getSource(ITYPE_STATUS_BAR).getFrame());
+        assertEquals(new Rect(0, 1, 2, 3), state.peekSource(ITYPE_STATUS_BAR).getFrame());
     }
 
     @Test
@@ -348,16 +345,17 @@
 
         final InsetsState rotatedState = new InsetsState(app.getInsetsState(),
                 true /* copySources */);
+        rotatedState.getOrCreateSource(ITYPE_STATUS_BAR, statusBars());
         spyOn(app.mToken);
         doReturn(rotatedState).when(app.mToken).getFixedRotationTransformInsetsState();
-        assertTrue(rotatedState.getSource(ITYPE_STATUS_BAR).isVisible());
+        assertTrue(rotatedState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
 
         provider.getSource().setVisible(false);
         mDisplayContent.getInsetsPolicy().showTransient(new int[] { ITYPE_STATUS_BAR },
                 true /* isGestureOnSystemBar */);
 
         assertTrue(mDisplayContent.getInsetsPolicy().isTransient(ITYPE_STATUS_BAR));
-        assertFalse(app.getInsetsState().getSource(ITYPE_STATUS_BAR).isVisible());
+        assertFalse(app.getInsetsState().isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
     }
 
     @Test
@@ -411,11 +409,11 @@
         final WindowState statusBar = createTestWindow("statusBar");
         final WindowState navBar = createTestWindow("navBar");
 
-        getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null);
+        getController().getSourceProvider(ID_IME).setWindowContainer(ime, null, null);
 
         waitUntilHandlersIdle();
         clearInvocations(mDisplayContent);
-        getController().getSourceProvider(ITYPE_IME).setClientVisible(true);
+        getController().getSourceProvider(ID_IME).setClientVisible(true);
         waitUntilHandlersIdle();
         // The visibility change should trigger a traversal to notify the change.
         verify(mDisplayContent).notifyInsetsChanged(any());
@@ -428,9 +426,9 @@
         getController().updateAboveInsetsState(false /* notifyInsetsChange */);
 
         // ime is below others.
-        assertNull(app.mAboveInsetsState.peekSource(ITYPE_IME));
-        assertNull(statusBar.mAboveInsetsState.peekSource(ITYPE_IME));
-        assertNull(navBar.mAboveInsetsState.peekSource(ITYPE_IME));
+        assertNull(app.mAboveInsetsState.peekSource(ID_IME));
+        assertNull(statusBar.mAboveInsetsState.peekSource(ID_IME));
+        assertNull(navBar.mAboveInsetsState.peekSource(ID_IME));
         assertNotNull(ime.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
         assertNotNull(ime.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR));
 
@@ -438,9 +436,9 @@
         getController().updateAboveInsetsState(true /* notifyInsetsChange */);
 
         // ime is above others.
-        assertNotNull(app.mAboveInsetsState.peekSource(ITYPE_IME));
-        assertNotNull(statusBar.mAboveInsetsState.peekSource(ITYPE_IME));
-        assertNotNull(navBar.mAboveInsetsState.peekSource(ITYPE_IME));
+        assertNotNull(app.mAboveInsetsState.peekSource(ID_IME));
+        assertNotNull(statusBar.mAboveInsetsState.peekSource(ID_IME));
+        assertNotNull(navBar.mAboveInsetsState.peekSource(ID_IME));
         assertNull(ime.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
         assertNull(ime.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR));
 
@@ -456,7 +454,7 @@
         final WindowState ime = createWindow(null,  TYPE_INPUT_METHOD, imeToken, "ime");
         final WindowState app = createTestWindow("app");
 
-        getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null);
+        getController().getSourceProvider(ID_IME).setWindowContainer(ime, null, null);
         ime.getControllableInsetProvider().setServerVisible(true);
 
         app.mActivityRecord.setVisibility(true);
@@ -468,7 +466,7 @@
         assertTrue(ime.getControllableInsetProvider().getSource().isVisible());
 
         getController().updateAboveInsetsState(true /* notifyInsetsChange */);
-        assertNotNull(app.getInsetsState().peekSource(ITYPE_IME));
+        assertNotNull(app.getInsetsState().peekSource(ID_IME));
         verify(app, atLeastOnce()).notifyInsetsChanged();
 
         // Expect the app will still get IME insets even when the app was invisible.
@@ -476,7 +474,7 @@
         app.mActivityRecord.setVisible(false);
         app.setHasSurface(false);
         getController().updateAboveInsetsState(true /* notifyInsetsChange */);
-        assertNotNull(app.getInsetsState().peekSource(ITYPE_IME));
+        assertNotNull(app.getInsetsState().peekSource(ID_IME));
         verify(app, atLeastOnce()).notifyInsetsChanged();
 
         // Expect the app will get IME insets when the app is requesting visible.
@@ -484,7 +482,7 @@
         app.mActivityRecord.setVisibility(true);
         assertTrue(app.isVisibleRequested());
         getController().updateAboveInsetsState(true /* notifyInsetsChange */);
-        assertNotNull(app.getInsetsState().peekSource(ITYPE_IME));
+        assertNotNull(app.getInsetsState().peekSource(ID_IME));
         verify(app, atLeastOnce()).notifyInsetsChanged();
     }
 
@@ -506,7 +504,7 @@
         final WindowState app2 = createTestWindow("app2");
 
         makeWindowVisible(mImeWindow);
-        final InsetsSourceProvider imeInsetsProvider = getController().getSourceProvider(ITYPE_IME);
+        final InsetsSourceProvider imeInsetsProvider = getController().getSourceProvider(ID_IME);
         imeInsetsProvider.setWindowContainer(mImeWindow, null, null);
         imeInsetsProvider.updateSourceFrame(mImeWindow.getFrame());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
index 22d72ed..c538727 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceSyncGroupTest.java
@@ -54,11 +54,11 @@
         final CountDownLatch finishedLatch = new CountDownLatch(1);
         SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
         syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
-        SyncTarget syncTarget = new SyncTarget();
+        SurfaceSyncGroup syncTarget = new SurfaceSyncGroup("FakeSyncTarget");
         syncGroup.add(syncTarget, null /* runnable */);
         syncGroup.markSyncReady();
 
-        syncTarget.onBufferReady();
+        syncTarget.markSyncReady();
 
         finishedLatch.await(5, TimeUnit.SECONDS);
         assertEquals(0, finishedLatch.getCount());
@@ -69,22 +69,22 @@
         final CountDownLatch finishedLatch = new CountDownLatch(1);
         SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
         syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
-        SyncTarget syncTarget1 = new SyncTarget();
-        SyncTarget syncTarget2 = new SyncTarget();
-        SyncTarget syncTarget3 = new SyncTarget();
+        SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
+        SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
+        SurfaceSyncGroup syncTarget3 = new SurfaceSyncGroup("FakeSyncTarget3");
 
         syncGroup.add(syncTarget1, null /* runnable */);
         syncGroup.add(syncTarget2, null /* runnable */);
         syncGroup.add(syncTarget3, null /* runnable */);
         syncGroup.markSyncReady();
 
-        syncTarget1.onBufferReady();
+        syncTarget1.markSyncReady();
         assertNotEquals(0, finishedLatch.getCount());
 
-        syncTarget3.onBufferReady();
+        syncTarget3.markSyncReady();
         assertNotEquals(0, finishedLatch.getCount());
 
-        syncTarget2.onBufferReady();
+        syncTarget2.markSyncReady();
 
         finishedLatch.await(5, TimeUnit.SECONDS);
         assertEquals(0, finishedLatch.getCount());
@@ -94,8 +94,8 @@
     public void testAddSyncWhenSyncComplete() {
         SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
 
-        SyncTarget syncTarget1 = new SyncTarget();
-        SyncTarget syncTarget2 = new SyncTarget();
+        SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
+        SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
 
         assertTrue(syncGroup.add(syncTarget1, null /* runnable */));
         syncGroup.markSyncReady();
@@ -114,21 +114,21 @@
         syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
         syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
 
-        SyncTarget syncTarget1 = new SyncTarget();
-        SyncTarget syncTarget2 = new SyncTarget();
+        SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
+        SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
 
         assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
         assertTrue(syncGroup2.add(syncTarget2, null /* runnable */));
         syncGroup1.markSyncReady();
         syncGroup2.markSyncReady();
 
-        syncTarget1.onBufferReady();
+        syncTarget1.markSyncReady();
 
         finishedLatch1.await(5, TimeUnit.SECONDS);
         assertEquals(0, finishedLatch1.getCount());
         assertNotEquals(0, finishedLatch2.getCount());
 
-        syncTarget2.onBufferReady();
+        syncTarget2.markSyncReady();
 
         finishedLatch2.await(5, TimeUnit.SECONDS);
         assertEquals(0, finishedLatch2.getCount());
@@ -144,8 +144,8 @@
         syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
         syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
 
-        SyncTarget syncTarget1 = new SyncTarget();
-        SyncTarget syncTarget2 = new SyncTarget();
+        SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
+        SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
 
         assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
         assertTrue(syncGroup2.add(syncTarget2, null /* runnable */));
@@ -155,12 +155,12 @@
 
         // Finish syncTarget2 first to test that the syncGroup is not complete until the merged sync
         // is also done.
-        syncTarget2.onBufferReady();
+        syncTarget2.markSyncReady();
         finishedLatch2.await(1, TimeUnit.SECONDS);
         // Sync did not complete yet
         assertNotEquals(0, finishedLatch2.getCount());
 
-        syncTarget1.onBufferReady();
+        syncTarget1.markSyncReady();
 
         // The first sync will still get a callback when it's sync requirements are done.
         finishedLatch1.await(5, TimeUnit.SECONDS);
@@ -180,13 +180,13 @@
         syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
         syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
 
-        SyncTarget syncTarget1 = new SyncTarget();
-        SyncTarget syncTarget2 = new SyncTarget();
+        SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
+        SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
 
         assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
         assertTrue(syncGroup2.add(syncTarget2, null /* runnable */));
         syncGroup1.markSyncReady();
-        syncTarget1.onBufferReady();
+        syncTarget1.markSyncReady();
 
         // The first sync will still get a callback when it's sync requirements are done.
         finishedLatch1.await(5, TimeUnit.SECONDS);
@@ -194,7 +194,7 @@
 
         syncGroup2.add(syncGroup1, null /* runnable */);
         syncGroup2.markSyncReady();
-        syncTarget2.onBufferReady();
+        syncTarget2.markSyncReady();
 
         // Verify that the second sync will receive complete since the merged sync was already
         // completed before the merge.
@@ -212,9 +212,9 @@
         syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
         syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
 
-        SyncTarget syncTarget1 = new SyncTarget();
-        SyncTarget syncTarget2 = new SyncTarget();
-        SyncTarget syncTarget3 = new SyncTarget();
+        SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
+        SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
+        SurfaceSyncGroup syncTarget3 = new SurfaceSyncGroup("FakeSyncTarget3");
 
         assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
         assertTrue(syncGroup1.add(syncTarget2, null /* runnable */));
@@ -228,8 +228,8 @@
 
         // Make target1 and target3 ready, but not target2. SyncGroup2 should not be ready since
         // SyncGroup2 also waits for all of SyncGroup1 to finish, which includes target2
-        syncTarget1.onBufferReady();
-        syncTarget3.onBufferReady();
+        syncTarget1.markSyncReady();
+        syncTarget3.markSyncReady();
 
         // Neither SyncGroup will be ready.
         finishedLatch1.await(1, TimeUnit.SECONDS);
@@ -238,7 +238,7 @@
         assertEquals(1, finishedLatch1.getCount());
         assertEquals(1, finishedLatch2.getCount());
 
-        syncTarget2.onBufferReady();
+        syncTarget2.markSyncReady();
 
         // Both sync groups should be ready after target2 completed.
         finishedLatch1.await(5, TimeUnit.SECONDS);
@@ -257,13 +257,13 @@
         syncGroup1.addSyncCompleteCallback(mExecutor, finishedLatch1::countDown);
         syncGroup2.addSyncCompleteCallback(mExecutor, finishedLatch2::countDown);
 
-        SyncTarget syncTarget1 = new SyncTarget();
-        SyncTarget syncTarget2 = new SyncTarget();
-        SyncTarget syncTarget3 = new SyncTarget();
+        SurfaceSyncGroup syncTarget1 = new SurfaceSyncGroup("FakeSyncTarget1");
+        SurfaceSyncGroup syncTarget2 = new SurfaceSyncGroup("FakeSyncTarget2");
+        SurfaceSyncGroup syncTarget3 = new SurfaceSyncGroup("FakeSyncTarget3");
 
         assertTrue(syncGroup1.add(syncTarget1, null /* runnable */));
         assertTrue(syncGroup1.add(syncTarget2, null /* runnable */));
-        syncTarget2.onBufferReady();
+        syncTarget2.markSyncReady();
 
         // Add syncTarget1 to syncGroup2 so it forces syncGroup1 into syncGroup2
         assertTrue(syncGroup2.add(syncTarget1, null /* runnable */));
@@ -272,7 +272,7 @@
         syncGroup1.markSyncReady();
         syncGroup2.markSyncReady();
 
-        syncTarget1.onBufferReady();
+        syncTarget1.markSyncReady();
 
         // Only SyncGroup1 will be ready, but SyncGroup2 still needs its own targets to be ready.
         finishedLatch1.await(1, TimeUnit.SECONDS);
@@ -281,7 +281,7 @@
         assertEquals(0, finishedLatch1.getCount());
         assertEquals(1, finishedLatch2.getCount());
 
-        syncTarget3.onBufferReady();
+        syncTarget3.markSyncReady();
 
         // SyncGroup2 is finished after target3 completed.
         finishedLatch2.await(1, TimeUnit.SECONDS);
@@ -302,7 +302,7 @@
         SurfaceControl.Transaction targetTransaction = spy(new StubTransaction());
         SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
 
-        SyncTarget syncTarget = new SyncTarget();
+        SurfaceSyncGroup syncTarget = new SurfaceSyncGroup("FakeSyncTarget");
         assertTrue(
                 syncGroup.add(syncTarget.mISurfaceSyncGroup, true /* parentSyncGroupMerge */,
                         null /* runnable */));
@@ -329,7 +329,7 @@
         SurfaceControl.Transaction targetTransaction = spy(new StubTransaction());
         SurfaceSyncGroup.setTransactionFactory(() -> targetTransaction);
 
-        SyncTarget syncTarget = new SyncTarget();
+        SurfaceSyncGroup syncTarget = new SurfaceSyncGroup("FakeSyncTarget");
         assertTrue(syncGroup.add(syncTarget, null /* runnable */));
         syncTarget.markSyncReady();
 
@@ -344,13 +344,13 @@
         final CountDownLatch finishedLatch = new CountDownLatch(1);
         SurfaceSyncGroup syncGroup = new SurfaceSyncGroup(TAG);
         syncGroup.addSyncCompleteCallback(mExecutor, finishedLatch::countDown);
-        SyncTarget syncTarget = new SyncTarget();
+        SurfaceSyncGroup syncTarget = new SurfaceSyncGroup("FakeSyncTarget");
         syncGroup.add(syncTarget, null /* runnable */);
         // Add the syncTarget to the same syncGroup and ensure it doesn't crash.
         syncGroup.add(syncTarget, null /* runnable */);
         syncGroup.markSyncReady();
 
-        syncTarget.onBufferReady();
+        syncTarget.markSyncReady();
 
         try {
             finishedLatch.await(5, TimeUnit.SECONDS);
@@ -359,14 +359,4 @@
         }
         assertEquals(0, finishedLatch.getCount());
     }
-
-    private static class SyncTarget extends SurfaceSyncGroup {
-        SyncTarget() {
-            super("FakeSyncTarget");
-        }
-
-        void onBufferReady() {
-            markSyncReady();
-        }
-    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index e7813ff..2bfc5ec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -56,6 +56,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.Gravity;
 import android.view.InsetsState;
+import android.view.WindowInsets;
 
 import androidx.test.filters.SmallTest;
 
@@ -1918,16 +1919,20 @@
 
         state.setDisplayFrame(displayFrame);
         if (sl > dl) {
-            state.getSource(ITYPE_CLIMATE_BAR).setFrame(dl, dt, sl, db);
+            state.getOrCreateSource(ITYPE_CLIMATE_BAR, WindowInsets.Type.statusBars())
+                    .setFrame(dl, dt, sl, db);
         }
         if (st > dt) {
-            state.getSource(ITYPE_STATUS_BAR).setFrame(dl, dt, dr, st);
+            state.getOrCreateSource(ITYPE_STATUS_BAR, WindowInsets.Type.statusBars())
+                    .setFrame(dl, dt, dr, st);
         }
         if (sr < dr) {
-            state.getSource(ITYPE_EXTRA_NAVIGATION_BAR).setFrame(sr, dt, dr, db);
+            state.getOrCreateSource(ITYPE_EXTRA_NAVIGATION_BAR, WindowInsets.Type.navigationBars())
+                    .setFrame(sr, dt, dr, db);
         }
         if (sb < db) {
-            state.getSource(ITYPE_NAVIGATION_BAR).setFrame(dl, sb, dr, db);
+            state.getOrCreateSource(ITYPE_NAVIGATION_BAR, WindowInsets.Type.navigationBars())
+                    .setFrame(dl, sb, dr, db);
         }
         // Recompute config and push to children.
         display.onRequestedOverrideConfigurationChanged(display.getConfiguration());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
index 19da718..1e5ec4c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.statusBars;
@@ -47,7 +47,7 @@
 
     private InsetsSource mSource = new InsetsSource(ITYPE_STATUS_BAR, statusBars());
     private WindowContainerInsetsSourceProvider mProvider;
-    private InsetsSource mImeSource = new InsetsSource(ITYPE_IME, ime());
+    private InsetsSource mImeSource = new InsetsSource(ID_IME, ime());
     private WindowContainerInsetsSourceProvider mImeProvider;
 
     @Before
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
index 56c59cc..731a235 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
@@ -16,12 +16,13 @@
 
 package com.android.server.wm;
 
-import static android.view.InsetsState.ITYPE_BOTTOM_DISPLAY_CUTOUT;
-import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
+import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowLayout.UNSPECIFIED_LENGTH;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
@@ -40,6 +41,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.DisplayCutout;
 import android.view.Gravity;
+import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.WindowInsets;
 import android.view.WindowLayout;
@@ -88,9 +90,9 @@
         mAttrs = new WindowManager.LayoutParams();
         mState = new InsetsState();
         mState.setDisplayFrame(new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
-        mState.getSource(ITYPE_STATUS_BAR).setFrame(
+        mState.getOrCreateSource(ITYPE_STATUS_BAR, statusBars()).setFrame(
                 0, 0, DISPLAY_WIDTH, STATUS_BAR_HEIGHT);
-        mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(
+        mState.getOrCreateSource(ITYPE_NAVIGATION_BAR, navigationBars()).setFrame(
                 0, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT);
         mState.getDisplayCutoutSafe(mDisplayCutoutSafe);
         mWindowBounds = new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
@@ -117,14 +119,14 @@
                 new Rect(),
                 WATERFALL_INSETS));
         mState.getDisplayCutoutSafe(mDisplayCutoutSafe);
-        mState.getSource(ITYPE_LEFT_DISPLAY_CUTOUT).setFrame(
-                0, 0, mDisplayCutoutSafe.left, DISPLAY_HEIGHT);
-        mState.getSource(ITYPE_TOP_DISPLAY_CUTOUT).setFrame(
-                0, 0, DISPLAY_WIDTH, mDisplayCutoutSafe.top);
-        mState.getSource(ITYPE_RIGHT_DISPLAY_CUTOUT).setFrame(
-                mDisplayCutoutSafe.right, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
-        mState.getSource(ITYPE_BOTTOM_DISPLAY_CUTOUT).setFrame(
-                0, mDisplayCutoutSafe.bottom, DISPLAY_WIDTH, DISPLAY_HEIGHT);
+        mState.getOrCreateSource(InsetsSource.createId(null, 0, displayCutout()), displayCutout())
+                .setFrame(0, 0, mDisplayCutoutSafe.left, DISPLAY_HEIGHT);
+        mState.getOrCreateSource(InsetsSource.createId(null, 1, displayCutout()), displayCutout())
+                .setFrame(0, 0, DISPLAY_WIDTH, mDisplayCutoutSafe.top);
+        mState.getOrCreateSource(InsetsSource.createId(null, 2, displayCutout()), displayCutout())
+                .setFrame(mDisplayCutoutSafe.right, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
+        mState.getOrCreateSource(InsetsSource.createId(null, 3, displayCutout()), displayCutout())
+                .setFrame(0, mDisplayCutoutSafe.bottom, DISPLAY_WIDTH, DISPLAY_HEIGHT);
     }
 
     private static void assertInsetByTopBottom(int top, int bottom, Rect actual) {
@@ -266,8 +268,8 @@
 
     @Test
     public void fitInvisibleInsets() {
-        mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
-        mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(false);
+        mState.setSourceVisible(ITYPE_STATUS_BAR, false);
+        mState.setSourceVisible(ITYPE_NAVIGATION_BAR, false);
         computeFrames();
 
         assertInsetByTopBottom(0, 0, mFrames.displayFrame);
@@ -277,8 +279,8 @@
 
     @Test
     public void fitInvisibleInsetsIgnoringVisibility() {
-        mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
-        mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(false);
+        mState.setSourceVisible(ITYPE_STATUS_BAR, false);
+        mState.setSourceVisible(ITYPE_NAVIGATION_BAR, false);
         mAttrs.setFitInsetsIgnoringVisibility(true);
         computeFrames();
 
@@ -289,9 +291,9 @@
 
     @Test
     public void insetParentFrameByIme() {
-        mState.getSource(InsetsState.ITYPE_IME).setVisible(true);
-        mState.getSource(InsetsState.ITYPE_IME).setFrame(
-                0, DISPLAY_HEIGHT - IME_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT);
+        mState.getOrCreateSource(ID_IME, ime())
+                .setVisible(true)
+                .setFrame(0, DISPLAY_HEIGHT - IME_HEIGHT, DISPLAY_WIDTH, DISPLAY_HEIGHT);
         mAttrs.privateFlags |= PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
         computeFrames();
 
@@ -363,8 +365,8 @@
     @Test
     public void layoutInDisplayCutoutModeDefaultWithInvisibleSystemBars() {
         addDisplayCutout();
-        mState.getSource(ITYPE_STATUS_BAR).setVisible(false);
-        mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(false);
+        mState.setSourceVisible(ITYPE_STATUS_BAR, false);
+        mState.setSourceVisible(ITYPE_NAVIGATION_BAR, false);
         mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
         mAttrs.setFitInsetsTypes(0);
         computeFrames();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index d3e5a8a..c44869b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -20,7 +20,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.Surface.ROTATION_0;
@@ -1039,12 +1039,16 @@
         mAppWindow.mAboveInsetsState.addSource(navSource);
 
         navSource.setVisible(false);
-        assertTrue(mImeWindow.getInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
-        assertFalse(mAppWindow.getInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
+        assertTrue(mImeWindow.getInsetsState().isSourceOrDefaultVisible(
+                ITYPE_NAVIGATION_BAR, navigationBars()));
+        assertFalse(mAppWindow.getInsetsState().isSourceOrDefaultVisible(
+                ITYPE_NAVIGATION_BAR, navigationBars()));
 
         navSource.setVisible(true);
-        assertTrue(mImeWindow.getInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
-        assertTrue(mAppWindow.getInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR));
+        assertTrue(mImeWindow.getInsetsState().isSourceOrDefaultVisible(
+                ITYPE_NAVIGATION_BAR, navigationBars()));
+        assertTrue(mAppWindow.getInsetsState().isSourceOrDefaultVisible(
+                ITYPE_NAVIGATION_BAR, navigationBars()));
     }
 
     @Test
@@ -1069,8 +1073,8 @@
         controller.updateAboveInsetsState(false);
 
         // Expect all app windows behind IME can receive IME insets visible.
-        assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
-        assertTrue(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
+        assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertTrue(app2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
 
         // Simulate app plays closing transition to app2.
         app.mActivityRecord.commitVisibility(false, false);
@@ -1078,8 +1082,8 @@
         assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
 
         // Verify the IME insets is visible on app, but not for app2 during app task switching.
-        assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
-        assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
+        assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertFalse(app2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
     }
 
     @Test
@@ -1108,8 +1112,8 @@
 
         // Expect app windows behind IME can receive IME insets visible,
         // but not for app2 in background.
-        assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
-        assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
+        assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertFalse(app2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
 
         // Simulate app plays closing transition to app2.
         // And app2 is now IME layering target but not yet to be the IME input target.
@@ -1119,8 +1123,8 @@
         assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
 
         // Verify the IME insets is still visible on app, but not for app2 during task switching.
-        assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
-        assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
+        assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
+        assertFalse(app2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime()));
     }
 
     @SetupWindows(addWindows = W_ACTIVITY)
@@ -1165,18 +1169,18 @@
         mNotificationShadeWindow.setHasSurface(true);
         mNotificationShadeWindow.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE;
         assertTrue(mNotificationShadeWindow.canBeImeTarget());
-        mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindowContainer(
+        mDisplayContent.getInsetsStateController().getSourceProvider(ID_IME).setWindowContainer(
                 mImeWindow, null, null);
 
         mDisplayContent.computeImeTarget(true);
         assertEquals(mNotificationShadeWindow, mDisplayContent.getImeTarget(IME_TARGET_LAYERING));
         mDisplayContent.getInsetsStateController().getRawInsetsState()
-                .setSourceVisible(ITYPE_IME, true);
+                .setSourceVisible(ID_IME, true);
 
         // Verify notificationShade can still get IME insets even windowing mode is multi-window.
         InsetsState state = mNotificationShadeWindow.getInsetsState();
-        assertNotNull(state.peekSource(ITYPE_IME));
-        assertTrue(state.getSource(ITYPE_IME).isVisible());
+        assertNotNull(state.peekSource(ID_IME));
+        assertTrue(state.isSourceOrDefaultVisible(ID_IME, ime()));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index 3ff791b..bd63560 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.view.InsetsState.ITYPE_IME;
+import static android.view.InsetsSource.ID_IME;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -262,7 +262,7 @@
     @Test
     public void testSetInsetsFrozen_notAffectImeWindowState() {
         // Pre-condition: make the IME window be controlled by IME insets provider.
-        mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_IME).setWindowContainer(
+        mDisplayContent.getInsetsStateController().getSourceProvider(ID_IME).setWindowContainer(
                 mDisplayContent.mInputMethodWindow, null, null);
 
         // Simulate an app window to be the IME layering target, assume the app window has no
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index e1de58e..aa1d556 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -119,11 +119,11 @@
     /**
      * List of connected MIDI devices
      */
-    private final HashMap<String, UsbMidiDevice>
-            mMidiDevices = new HashMap<String, UsbMidiDevice>();
+    private final HashMap<String, UsbAlsaMidiDevice>
+            mMidiDevices = new HashMap<String, UsbAlsaMidiDevice>();
 
-    // UsbMidiDevice for USB peripheral mode (gadget) device
-    private UsbMidiDevice mPeripheralMidiDevice = null;
+    // UsbAlsaMidiDevice for USB peripheral mode (gadget) device
+    private UsbAlsaMidiDevice mPeripheralMidiDevice = null;
 
     private final HashSet<Integer> mAlsaCards = new HashSet<>();
     private final FileObserver mAlsaObserver = new FileObserver(new File(ALSA_DIRECTORY),
@@ -331,11 +331,11 @@
                 Slog.d(TAG, "numLegacyMidiOutputs:" + numLegacyMidiOutputs);
             }
 
-            UsbMidiDevice usbMidiDevice = UsbMidiDevice.create(mContext, properties,
+            UsbAlsaMidiDevice midiDevice = UsbAlsaMidiDevice.create(mContext, properties,
                     cardRec.getCardNum(), 0 /*device*/, numLegacyMidiInputs,
                     numLegacyMidiOutputs);
-            if (usbMidiDevice != null) {
-                mMidiDevices.put(deviceAddress, usbMidiDevice);
+            if (midiDevice != null) {
+                mMidiDevices.put(deviceAddress, midiDevice);
             }
         }
     }
@@ -355,10 +355,10 @@
         }
 
         // MIDI
-        UsbMidiDevice usbMidiDevice = mMidiDevices.remove(deviceAddress);
-        if (usbMidiDevice != null) {
+        UsbAlsaMidiDevice midiDevice = mMidiDevices.remove(deviceAddress);
+        if (midiDevice != null) {
             Slog.i(TAG, "USB MIDI Device Removed: " + deviceAddress);
-            IoUtils.closeQuietly(usbMidiDevice);
+            IoUtils.closeQuietly(midiDevice);
         }
 
         logDevices("usbDeviceRemoved()");
@@ -381,7 +381,7 @@
                     com.android.internal.R.string.usb_midi_peripheral_product_name));
             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_CARD, card);
             properties.putInt(MidiDeviceInfo.PROPERTY_ALSA_DEVICE, device);
-            mPeripheralMidiDevice = UsbMidiDevice.create(mContext, properties, card, device,
+            mPeripheralMidiDevice = UsbAlsaMidiDevice.create(mContext, properties, card, device,
                     1 /* numInputs */, 1 /* numOutputs */);
         } else if (!enabled && mPeripheralMidiDevice != null) {
             IoUtils.closeQuietly(mPeripheralMidiDevice);
@@ -500,9 +500,9 @@
         }
 
         for (String deviceAddr : mMidiDevices.keySet()) {
-            // A UsbMidiDevice does not have a handle to the UsbDevice anymore
-            mMidiDevices.get(deviceAddr).dump(deviceAddr, dump, "midi_devices",
-                    UsbAlsaManagerProto.MIDI_DEVICES);
+            // A UsbAlsaMidiDevice does not have a handle to the UsbDevice anymore
+            mMidiDevices.get(deviceAddr).dump(deviceAddr, dump, "alsa_midi_devices",
+                    UsbAlsaManagerProto.ALSA_MIDI_DEVICES);
         }
 
         dump.end(token);
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaMidiDevice.java
similarity index 91%
rename from services/usb/java/com/android/server/usb/UsbMidiDevice.java
rename to services/usb/java/com/android/server/usb/UsbAlsaMidiDevice.java
index d9ad703..e92c034 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaMidiDevice.java
@@ -10,7 +10,7 @@
  * 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 an
+ * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
@@ -24,7 +24,7 @@
 import android.media.midi.MidiManager;
 import android.media.midi.MidiReceiver;
 import android.os.Bundle;
-import android.service.usb.UsbMidiDeviceProto;
+import android.service.usb.UsbAlsaMidiDeviceProto;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
@@ -43,8 +43,12 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 
-public final class UsbMidiDevice implements Closeable {
-    private static final String TAG = "UsbMidiDevice";
+/**
+ * Opens device connections to MIDI 1.0 endpoints.
+ * These endpoints will use ALSA.
+ */
+public final class UsbAlsaMidiDevice implements Closeable {
+    private static final String TAG = "UsbAlsaMidiDevice";
 
     private final int mAlsaCard;
     private final int mAlsaDevice;
@@ -142,13 +146,13 @@
     }
 
     /**
-     * Creates an UsbMidiDevice based on the input parameters. Read/Write streams
+     * Creates an UsbAlsaMidiDevice based on the input parameters. Read/Write streams
      * will be created individually as some devices don't have the same number of
      * inputs and outputs.
      */
-    public static UsbMidiDevice create(Context context, Bundle properties, int card,
+    public static UsbAlsaMidiDevice create(Context context, Bundle properties, int card,
             int device, int numInputs, int numOutputs) {
-        UsbMidiDevice midiDevice = new UsbMidiDevice(card, device, numInputs, numOutputs);
+        UsbAlsaMidiDevice midiDevice = new UsbAlsaMidiDevice(card, device, numInputs, numOutputs);
         if (!midiDevice.register(context, properties)) {
             IoUtils.closeQuietly(midiDevice);
             Log.e(TAG, "createDeviceServer failed");
@@ -157,7 +161,7 @@
         return midiDevice;
     }
 
-    private UsbMidiDevice(int card, int device, int numInputs, int numOutputs) {
+    private UsbAlsaMidiDevice(int card, int device, int numInputs, int numOutputs) {
         mAlsaCard = card;
         mAlsaDevice = device;
         mNumInputs = numInputs;
@@ -217,7 +221,7 @@
 
         if (inputStreamCount > 0) {
             // Create input thread which will read from all output ports of the physical device
-            new Thread("UsbMidiDevice input thread") {
+            new Thread("UsbAlsaMidiDevice input thread") {
                 @Override
                 public void run() {
                     byte[] buffer = new byte[BUFFER_SIZE];
@@ -272,13 +276,13 @@
             final FileOutputStream outputStreamF = mOutputStreams[port];
             final int portF = port;
 
-            new Thread("UsbMidiDevice output thread " + port) {
+            new Thread("UsbAlsaMidiDevice output thread " + port) {
                 @Override
                 public void run() {
                     while (true) {
                         MidiEvent event;
                         try {
-                            event = (MidiEvent)eventSchedulerF.waitNextEvent();
+                            event = (MidiEvent) eventSchedulerF.waitNextEvent();
                         } catch (InterruptedException e) {
                             // try again
                             continue;
@@ -303,9 +307,9 @@
     }
 
     private boolean register(Context context, Bundle properties) {
-        MidiManager midiManager = (MidiManager)context.getSystemService(Context.MIDI_SERVICE);
+        MidiManager midiManager = context.getSystemService(MidiManager.class);
         if (midiManager == null) {
-            Log.e(TAG, "No MidiManager in UsbMidiDevice.register()");
+            Log.e(TAG, "No MidiManager in UsbAlsaMidiDevice.register()");
             return false;
         }
 
@@ -365,9 +369,9 @@
             long id) {
         long token = dump.start(idName, id);
 
-        dump.write("device_address", UsbMidiDeviceProto.DEVICE_ADDRESS, deviceAddr);
-        dump.write("card", UsbMidiDeviceProto.CARD, mAlsaCard);
-        dump.write("device", UsbMidiDeviceProto.DEVICE, mAlsaDevice);
+        dump.write("device_address", UsbAlsaMidiDeviceProto.DEVICE_ADDRESS, deviceAddr);
+        dump.write("card", UsbAlsaMidiDeviceProto.CARD, mAlsaCard);
+        dump.write("device", UsbAlsaMidiDeviceProto.DEVICE, mAlsaDevice);
 
         dump.end(token);
     }
diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
index 8db3d00..6fe548f 100644
--- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
@@ -16,17 +16,20 @@
 
 package android.transparency.test;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
 
-import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -40,12 +43,11 @@
     private static final String JOB_ID = "1740526926";
 
     /** Waiting time for the job to be scheduled */
-    private static final int JOB_CREATION_MAX_SECONDS = 5;
+    private static final int JOB_CREATION_MAX_SECONDS = 30;
 
-    @After
-    public void tearDown() throws Exception {
-        uninstallPackage("com.android.egg");
-        uninstallRebootlessApex();
+    @Before
+    public void setUp() throws Exception {
+        cancelPendingJob();
     }
 
     @Test
@@ -68,35 +70,41 @@
 
     @Test
     public void testCollectAllUpdatedPreloadInfo() throws Exception {
-        installPackage("EasterEgg.apk");
-        runDeviceTest("testCollectAllUpdatedPreloadInfo");
+        try {
+            updatePreloadApp();
+            runDeviceTest("testCollectAllUpdatedPreloadInfo");
+        } finally {
+            // No need to wait until job complete, since we can't verifying very meaningfully.
+            cancelPendingJob();
+            uninstallPackage("com.android.egg");
+        }
     }
 
     @Test
     public void testRebootlessApexUpdateTriggersJobScheduling() throws Exception {
-        cancelPendingJob();
-        installRebootlessApex();
+        try {
+            installRebootlessApex();
 
-        // Verify
-        expectJobToBeScheduled();
-        // Just cancel since we can't verifying very meaningfully.
-        cancelPendingJob();
+            // Verify
+            expectJobToBeScheduled();
+        } finally {
+            // No need to wait until job complete, since we can't verifying very meaningfully.
+            uninstallRebootlessApexThenReboot();
+        }
     }
 
     @Test
     public void testPreloadUpdateTriggersJobScheduling() throws Exception {
-        cancelPendingJob();
-        installPackage("EasterEgg.apk");
+        try {
+            updatePreloadApp();
 
-        // Verify
-        expectJobToBeScheduled();
-        // Just cancel since we can't verifying very meaningfully.
-        cancelPendingJob();
-    }
-
-    @Test
-    public void testMeasureMbas() throws Exception {
-        // TODO(265244016): figure out a way to install an MBA
+            // Verify
+            expectJobToBeScheduled();
+        } finally {
+            // No need to wait until job complete, since we can't verifying very meaningfully.
+            cancelPendingJob();
+            uninstallPackage("com.android.egg");
+        }
     }
 
     private void runDeviceTest(String method) throws DeviceNotAvailableException {
@@ -109,7 +117,11 @@
     private void cancelPendingJob() throws DeviceNotAvailableException {
         CommandResult result = getDevice().executeShellV2Command(
                 "cmd jobscheduler cancel android " + JOB_ID);
-        assertTrue(result.getStatus() == CommandStatus.SUCCESS);
+        if (result.getStatus() == CommandStatus.SUCCESS) {
+            CLog.d("Canceling, output: " + result.getStdout());
+        } else {
+            CLog.d("Something went wrong, error: " + result.getStderr());
+        }
     }
 
     private void expectJobToBeScheduled() throws Exception {
@@ -117,6 +129,7 @@
             CommandResult result = getDevice().executeShellV2Command(
                     "cmd jobscheduler get-job-state android " + JOB_ID);
             String state = result.getStdout().toString();
+            CLog.i("Job status: " + state);
             if (state.startsWith("unknown")) {
                 // The job hasn't been scheduled yet. So try again.
                 TimeUnit.SECONDS.sleep(1);
@@ -134,7 +147,7 @@
         installPackage("com.android.apex.cts.shim.v2_rebootless.apex", "--force-non-staged");
     }
 
-    private void uninstallRebootlessApex() throws DeviceNotAvailableException {
+    private void uninstallRebootlessApexThenReboot() throws DeviceNotAvailableException {
         // Reboot only if the APEX is not the pre-install one.
         CommandResult result = getDevice().executeShellV2Command(
                 "pm list packages -f --apex-only |grep com.android.apex.cts.shim");
@@ -148,4 +161,14 @@
             assertTrue(runResult.getStatus() == CommandStatus.SUCCESS);
         }
     }
+
+    private void updatePreloadApp() throws DeviceNotAvailableException {
+        CommandResult result = getDevice().executeShellV2Command("pm path com.android.egg");
+        assertTrue(result.getStatus() == CommandStatus.SUCCESS);
+        assertThat(result.getStdout()).startsWith("package:/system/app/");
+        String path = result.getStdout().replaceFirst("^package:", "");
+
+        result = getDevice().executeShellV2Command("pm install " + path);
+        assertTrue(result.getStatus() == CommandStatus.SUCCESS);
+    }
 }
diff --git a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
index aedb366..176bc28e 100644
--- a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
+++ b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
@@ -33,6 +33,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.HexFormat;
 import java.util.stream.Collectors;
@@ -56,11 +57,12 @@
         assertThat(args).isNotNull();
         int number = Integer.valueOf(args.getString("apex-number"));
         assertThat(number).isGreaterThan(0);
-        var expectedApexNames = new HashSet<String>();
+        var expectedApexNames = new ArrayList<String>();
         for (var i = 0; i < number; i++) {
             String moduleName = args.getString("apex-" + Integer.toString(i));
             expectedApexNames.add(moduleName);
         }
+        assertThat(expectedApexNames).containsNoDuplicates();
 
         // Action
         var apexInfoList = mBt.collectAllApexInfo(/* includeTestOnly */ true);
diff --git a/tests/ChoreographerTests/Android.bp b/tests/ChoreographerTests/Android.bp
new file mode 100644
index 0000000..ff252f7
--- /dev/null
+++ b/tests/ChoreographerTests/Android.bp
@@ -0,0 +1,46 @@
+// Copyright 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "ChoreographerTests",
+    srcs: ["src/**/*.java"],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "com.google.android.material_material",
+        "truth-prebuilt",
+    ],
+    resource_dirs: ["src/main/res"],
+    certificate: "platform",
+    platform_apis: true,
+    test_suites: ["device-tests"],
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/tests/ChoreographerTests/AndroidManifest.xml b/tests/ChoreographerTests/AndroidManifest.xml
new file mode 100644
index 0000000..3283c90
--- /dev/null
+++ b/tests/ChoreographerTests/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.view.choreographertests">
+
+    <application android:debuggable="true" android:testOnly="true">
+        <uses-library android:name="android.test.runner"/>
+        <activity
+            android:name=".GraphicsActivity"
+            android:exported="false">
+        </activity>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.view.choreographertests"
+        android:label="Tests of android.view.ChoreographerTests">
+    </instrumentation>
+</manifest>
\ No newline at end of file
diff --git a/tests/ChoreographerTests/AndroidTest.xml b/tests/ChoreographerTests/AndroidTest.xml
new file mode 100644
index 0000000..e717699
--- /dev/null
+++ b/tests/ChoreographerTests/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Config for ChoreographerTests cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="test-tag" value="ChoreographerTests"/>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="ChoreographerTests.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.view.choreographertests" />
+        <option name="hidden-api-checks" value="false" />
+        <option name="isolated-storage" value="false" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/ChoreographerTests/OWNERS b/tests/ChoreographerTests/OWNERS
new file mode 100644
index 0000000..2b7de25
--- /dev/null
+++ b/tests/ChoreographerTests/OWNERS
@@ -0,0 +1,2 @@
+include platform/frameworks/base:/graphics/java/android/graphics/OWNERS
+include platform/frameworks/native:/services/surfaceflinger/OWNERS
\ No newline at end of file
diff --git a/tests/ChoreographerTests/TEST_MAPPING b/tests/ChoreographerTests/TEST_MAPPING
new file mode 100644
index 0000000..16a48ea
--- /dev/null
+++ b/tests/ChoreographerTests/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "ChoreographerTests"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java
new file mode 100644
index 0000000..44112fc
--- /dev/null
+++ b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/AttachedChoreographerTest.java
@@ -0,0 +1,474 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.choreographertests;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.Manifest;
+import android.app.compat.CompatChanges;
+import android.hardware.display.DisplayManager;
+import android.os.Looper;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+import android.view.Choreographer;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class AttachedChoreographerTest {
+    private static final String TAG = "AttachedChoreographerTest";
+    private static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID = 170503758;
+    private static final int THRESHOLD_MS = 10;
+    private static final int CALLBACK_TIME_10_FPS = 100;
+    private static final int CALLBACK_TIME_30_FPS = 33;
+    private static final int FRAME_ITERATIONS = 21;
+    private static final int CALLBACK_MISSED_THRESHOLD = 2;
+
+    private final CountDownLatch mTestCompleteSignal = new CountDownLatch(2);
+    private final CountDownLatch mSurfaceCreationCountDown = new CountDownLatch(1);
+    private final CountDownLatch mNoCallbackSignal = new CountDownLatch(1);
+    private final CountDownLatch mFramesSignal = new CountDownLatch(FRAME_ITERATIONS);
+
+    private ActivityScenario<GraphicsActivity> mScenario;
+    private int mInitialMatchContentFrameRate;
+    private DisplayManager mDisplayManager;
+    private SurfaceView mSurfaceView;
+    private SurfaceHolder mSurfaceHolder;
+    private boolean mIsFirstCallback = true;
+    private int mCallbackMissedCounter = 0;
+
+    @Before
+    public void setUp() throws Exception {
+        mScenario = ActivityScenario.launch(GraphicsActivity.class);
+        mScenario.moveToState(Lifecycle.State.CREATED);
+        mCallbackMissedCounter = 0;
+        mScenario.onActivity(activity -> {
+            mSurfaceView = activity.findViewById(R.id.surface);
+            mSurfaceHolder = mSurfaceView.getHolder();
+            mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
+
+                @Override
+                public void surfaceChanged(SurfaceHolder holder, int format, int width,
+                        int height) {
+                }
+
+                @Override
+                public void surfaceCreated(SurfaceHolder holder) {
+                    mSurfaceCreationCountDown.countDown();
+                }
+
+                @Override
+                public void surfaceDestroyed(SurfaceHolder holder) {
+                }
+            });
+        });
+
+
+        UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        uiDevice.wakeUp();
+        uiDevice.executeShellCommand("wm dismiss-keyguard");
+        mScenario.moveToState(Lifecycle.State.RESUMED);
+
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
+                        Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+                        Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE,
+                        Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS,
+                        Manifest.permission.MANAGE_GAME_MODE);
+        mScenario.onActivity(activity -> {
+            mDisplayManager = activity.getSystemService(DisplayManager.class);
+            mInitialMatchContentFrameRate = toSwitchingType(
+                    mDisplayManager.getMatchContentFrameRateUserPreference());
+            mDisplayManager.setRefreshRateSwitchingType(
+                    DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
+            mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
+            boolean changeIsEnabled =
+                    CompatChanges.isChangeEnabled(
+                            DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID);
+            Log.i(TAG, "DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGE_ID is "
+                    + (changeIsEnabled ? "enabled" : "disabled"));
+        });
+    }
+
+    @After
+    public void tearDown() {
+        mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate);
+        mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void test_create_choreographer() {
+        mScenario.onActivity(activity -> {
+            if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
+                fail("Unable to create surface within 1 Second");
+            }
+            SurfaceControl sc = mSurfaceView.getSurfaceControl();
+            mTestCompleteSignal.countDown();
+            SurfaceControl sc1 = new SurfaceControl(sc, "AttachedChoreographerTests");
+            // Create attached choreographer with getChoreographer
+            sc1.getChoreographer();
+            assertTrue(sc1.hasChoreographer());
+            assertTrue(sc1.isValid());
+            sc1.release();
+
+            SurfaceControl sc2 = new SurfaceControl(sc, "AttachedChoreographerTests");
+            // Create attached choreographer with Looper.myLooper
+            sc2.getChoreographer(Looper.myLooper());
+            assertTrue(sc2.hasChoreographer());
+            assertTrue(sc2.isValid());
+            sc2.release();
+
+            SurfaceControl sc3 = new SurfaceControl(sc, "AttachedChoreographerTests");
+            // Create attached choreographer with Looper.myLooper
+            sc3.getChoreographer(Looper.getMainLooper());
+            assertTrue(sc3.hasChoreographer());
+            assertTrue(sc3.isValid());
+            sc3.release();
+            mTestCompleteSignal.countDown();
+        });
+        if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) {
+            fail("Test not finished in 2 Seconds");
+        }
+    }
+
+    @Test
+    public void test_copy_surface_control() {
+        mScenario.onActivity(activity -> {
+            if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
+                fail("Unable to create surface within 1 Second");
+            }
+            SurfaceControl sc = mSurfaceView.getSurfaceControl();
+            // Create attached choreographer
+            sc.getChoreographer();
+            assertTrue(sc.hasChoreographer());
+
+            // Use copy constructor
+            SurfaceControl copyConstructorSc = new SurfaceControl(sc, "AttachedChoreographerTests");
+            //Choreographer isn't copied over.
+            assertFalse(copyConstructorSc.hasChoreographer());
+            copyConstructorSc.getChoreographer();
+            assertTrue(copyConstructorSc.hasChoreographer());
+            mTestCompleteSignal.countDown();
+
+            // Use copyFrom
+            SurfaceControl copyFromSc = new SurfaceControl();
+            copyFromSc.copyFrom(sc, "AttachedChoreographerTests");
+            //Choreographer isn't copied over.
+            assertFalse(copyFromSc.hasChoreographer());
+            copyFromSc.getChoreographer();
+            assertTrue(copyFromSc.hasChoreographer());
+            mTestCompleteSignal.countDown();
+        });
+        if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) {
+            fail("Test not finished in 2 Seconds");
+        }
+    }
+
+    @Test
+    public void test_mirror_surface_control() {
+        mScenario.onActivity(activity -> {
+            if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
+                fail("Unable to create surface within 1 Second");
+            }
+            SurfaceControl sc = mSurfaceView.getSurfaceControl();
+            // Create attached choreographer
+            sc.getChoreographer();
+            assertTrue(sc.hasChoreographer());
+            mTestCompleteSignal.countDown();
+
+            // Use mirrorSurface
+            SurfaceControl mirrorSc = SurfaceControl.mirrorSurface(sc);
+            //Choreographer isn't copied over.
+            assertFalse(mirrorSc.hasChoreographer());
+            mirrorSc.getChoreographer();
+            assertTrue(mirrorSc.hasChoreographer());
+            // make SurfaceControl invalid by releasing it.
+            mirrorSc.release();
+
+            assertTrue(sc.isValid());
+            assertFalse(mirrorSc.isValid());
+            assertFalse(mirrorSc.hasChoreographer());
+            assertThrows(NullPointerException.class, mirrorSc::getChoreographer);
+            mTestCompleteSignal.countDown();
+        });
+        if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) {
+            fail("Test not finished in 2 Seconds");
+        }
+    }
+
+    @Test
+    public void test_postFrameCallback() {
+        mScenario.onActivity(activity -> {
+            if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
+                fail("Unable to create surface within 1 Second");
+            }
+            SurfaceControl sc = mSurfaceView.getSurfaceControl();
+            sc.getChoreographer().postFrameCallback(
+                    frameTimeNanos -> mTestCompleteSignal.countDown());
+
+            SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests");
+            Choreographer copyChoreographer = copySc.getChoreographer();
+            // make SurfaceControl invalid by releasing it.
+            copySc.release();
+
+            assertTrue(sc.isValid());
+            assertFalse(copySc.isValid());
+            copyChoreographer.postFrameCallback(frameTimeNanos -> mNoCallbackSignal.countDown());
+            assertDoesReceiveCallback();
+        });
+        if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) {
+            fail("Test not finished in 2 Seconds");
+        }
+    }
+
+    @Test
+    public void test_postFrameCallbackDelayed() {
+        mScenario.onActivity(activity -> {
+            if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
+                fail("Unable to create surface within 1 Second");
+            }
+            SurfaceControl sc = mSurfaceView.getSurfaceControl();
+            sc.getChoreographer(Looper.getMainLooper()).postFrameCallbackDelayed(
+                    callback -> mTestCompleteSignal.countDown(),
+                    /* delayMillis */ 5);
+
+            SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests");
+            Choreographer copyChoreographer = copySc.getChoreographer();
+            // make SurfaceControl invalid by releasing it.
+            copySc.release();
+
+            assertTrue(sc.isValid());
+            assertFalse(copySc.isValid());
+            copyChoreographer.postFrameCallbackDelayed(
+                    frameTimeNanos -> mNoCallbackSignal.countDown(), /* delayMillis */5);
+            assertDoesReceiveCallback();
+        });
+        if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) {
+            fail("Test not finished in 2 Seconds");
+        }
+    }
+
+    @Test
+    public void test_postCallback() {
+        mScenario.onActivity(activity -> {
+            if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
+                fail("Unable to create surface within 1 Second");
+            }
+            SurfaceControl sc = mSurfaceView.getSurfaceControl();
+            sc.getChoreographer().postCallback(Choreographer.CALLBACK_COMMIT,
+                    mTestCompleteSignal::countDown, /* token */ this);
+
+            SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests");
+            Choreographer copyChoreographer = copySc.getChoreographer();
+            // make SurfaceControl invalid by releasing it.
+            copySc.release();
+
+            assertTrue(sc.isValid());
+            assertFalse(copySc.isValid());
+            copyChoreographer.postCallback(Choreographer.CALLBACK_COMMIT,
+                    mNoCallbackSignal::countDown, /* token */ this);
+            assertDoesReceiveCallback();
+        });
+        if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) {
+            fail("Test not finished in 2 Seconds");
+        }
+    }
+
+    @Test
+    public void test_postCallbackDelayed() {
+        mScenario.onActivity(activity -> {
+            if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
+                fail("Unable to create surface within 1 Second");
+            }
+            SurfaceControl sc = mSurfaceView.getSurfaceControl();
+            sc.getChoreographer().postCallbackDelayed(Choreographer.CALLBACK_COMMIT,
+                    mTestCompleteSignal::countDown, /* token */ this, /* delayMillis */ 5);
+
+            SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests");
+            Choreographer copyChoreographer = copySc.getChoreographer();
+            // make SurfaceControl invalid by releasing it.
+            copySc.release();
+
+            assertTrue(sc.isValid());
+            assertFalse(copySc.isValid());
+            copyChoreographer.postCallbackDelayed(Choreographer.CALLBACK_COMMIT,
+                    mNoCallbackSignal::countDown, /* token */ this, /* delayMillis */ 5);
+            assertDoesReceiveCallback();
+        });
+        if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) {
+            fail("Test not finished in 2 Seconds");
+        }
+    }
+
+    @Test
+    public void test_postVsyncCallback() {
+        mScenario.onActivity(activity -> {
+            if (waitForCountDown(mSurfaceCreationCountDown, /* timeout */ 1L)) {
+                fail("Unable to create surface within 1 Second");
+            }
+            SurfaceControl sc = mSurfaceView.getSurfaceControl();
+            sc.getChoreographer().postVsyncCallback(data -> mTestCompleteSignal.countDown());
+
+            SurfaceControl copySc = new SurfaceControl(sc, "AttachedChoreographerTests");
+            Choreographer copyChoreographer = copySc.getChoreographer();
+            // make SurfaceControl invalid by releasing it.
+            copySc.release();
+
+            assertTrue(sc.isValid());
+            assertFalse(copySc.isValid());
+            copyChoreographer.postVsyncCallback(data -> mNoCallbackSignal.countDown());
+            assertDoesReceiveCallback();
+        });
+        if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 2L)) {
+            fail("Test not finished in 2 Seconds");
+        }
+    }
+
+    @Test
+    public void test_choreographer_10Hz_refreshRate() {
+        mScenario.onActivity(activity -> {
+            if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
+                fail("Unable to create surface within 1 Second");
+            }
+            SurfaceControl sc = mSurfaceView.getSurfaceControl();
+            Choreographer choreographer = sc.getChoreographer();
+            SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+            transaction.setFrameRate(sc, 10.0f, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
+                    .addTransactionCommittedListener(Runnable::run,
+                            () -> verifyVsyncCallbacks(choreographer,
+                                    CALLBACK_TIME_10_FPS))
+                    .apply();
+            mTestCompleteSignal.countDown();
+        });
+        if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 5L)) {
+            fail("Test not finished in 5 Seconds");
+        }
+    }
+
+    @Test
+    public void test_choreographer_30Hz_refreshRate() {
+        mScenario.onActivity(activity -> {
+            if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
+                fail("Unable to create surface within 1 Second");
+            }
+            SurfaceControl sc = mSurfaceView.getSurfaceControl();
+            Choreographer choreographer = sc.getChoreographer();
+            SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+            transaction.setFrameRate(sc, 30.0f, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
+                    .addTransactionCommittedListener(Runnable::run,
+                            () -> verifyVsyncCallbacks(choreographer,
+                                    CALLBACK_TIME_30_FPS))
+                    .apply();
+            mTestCompleteSignal.countDown();
+        });
+        if (waitForCountDown(mTestCompleteSignal, /* timeoutInSeconds */ 5L)) {
+            fail("Test not finished in 5 Seconds");
+        }
+    }
+
+    private void verifyVsyncCallbacks(Choreographer choreographer, int callbackDurationMs) {
+        long callbackRequestedTimeNs = System.nanoTime();
+        choreographer.postVsyncCallback(frameData -> {
+            mFramesSignal.countDown();
+            final long frameCount = mFramesSignal.getCount();
+            if (frameCount > 0) {
+                if (!mIsFirstCallback) {
+                    // Skip the first callback as it takes 1 frame
+                    // to reflect the new refresh rate
+                    long callbackDurationDiffMs = getCallbackDurationDiffInMs(
+                            frameData.getFrameTimeNanos(),
+                            callbackRequestedTimeNs, callbackDurationMs);
+                    if (callbackDurationDiffMs < 0 || callbackDurationDiffMs > THRESHOLD_MS) {
+                        mCallbackMissedCounter++;
+                        Log.e(TAG, "Frame #" + Math.abs(frameCount - FRAME_ITERATIONS)
+                                + " vsync callback failed, expected callback in "
+                                + callbackDurationMs
+                                + " With threshold of " + THRESHOLD_MS
+                                + " but actual duration difference is " + callbackDurationDiffMs);
+                    }
+                }
+                mIsFirstCallback = false;
+                verifyVsyncCallbacks(choreographer, callbackDurationMs);
+            } else {
+                assertTrue("Missed timeline for " + mCallbackMissedCounter + " callbacks, while "
+                                + CALLBACK_MISSED_THRESHOLD + " missed callbacks are allowed",
+                        mCallbackMissedCounter <= CALLBACK_MISSED_THRESHOLD);
+                mTestCompleteSignal.countDown();
+            }
+        });
+    }
+
+    private long getCallbackDurationDiffInMs(long callbackTimeNs, long requestedTimeNs,
+            int expectedCallbackMs) {
+        long actualTimeMs = TimeUnit.NANOSECONDS.toMillis(callbackTimeNs)
+                - TimeUnit.NANOSECONDS.toMillis(requestedTimeNs);
+        return Math.abs(expectedCallbackMs - actualTimeMs);
+    }
+
+    private boolean waitForCountDown(CountDownLatch countDownLatch, long timeoutInSeconds) {
+        try {
+            return !countDownLatch.await(timeoutInSeconds, TimeUnit.SECONDS);
+        } catch (InterruptedException ex) {
+            throw new AssertionError("Test interrupted", ex);
+        }
+    }
+
+    private int toSwitchingType(int matchContentFrameRateUserPreference) {
+        switch (matchContentFrameRateUserPreference) {
+            case DisplayManager.MATCH_CONTENT_FRAMERATE_NEVER:
+                return DisplayManager.SWITCHING_TYPE_NONE;
+            case DisplayManager.MATCH_CONTENT_FRAMERATE_SEAMLESSS_ONLY:
+                return DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS;
+            case DisplayManager.MATCH_CONTENT_FRAMERATE_ALWAYS:
+                return DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS;
+            default:
+                return -1;
+        }
+    }
+
+    private void assertDoesReceiveCallback() {
+        try {
+            if (mNoCallbackSignal.await(/* timeout */ 50L, TimeUnit.MILLISECONDS)) {
+                fail("Callback not supposed to be generated");
+            } else {
+                mTestCompleteSignal.countDown();
+            }
+        } catch (InterruptedException e) {
+            fail("Callback wait is interrupted " + e);
+        }
+    }
+}
diff --git a/tests/ChoreographerTests/src/main/java/android/view/choreographertests/GraphicsActivity.java b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/GraphicsActivity.java
new file mode 100644
index 0000000..50a6850
--- /dev/null
+++ b/tests/ChoreographerTests/src/main/java/android/view/choreographertests/GraphicsActivity.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.choreographertests;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class GraphicsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_attached_choreographer);
+    }
+}
diff --git a/tests/ChoreographerTests/src/main/res/layout/activity_attached_choreographer.xml b/tests/ChoreographerTests/src/main/res/layout/activity_attached_choreographer.xml
new file mode 100644
index 0000000..d6c8212
--- /dev/null
+++ b/tests/ChoreographerTests/src/main/res/layout/activity_attached_choreographer.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <SurfaceView
+        android:id="@+id/surface"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"/>
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/tests/ChoreographerTests/src/main/res/mipmap-hdpi/ic_launcher.png b/tests/ChoreographerTests/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/tests/ChoreographerTests/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/ChoreographerTests/src/main/res/mipmap-mdpi/ic_launcher.png b/tests/ChoreographerTests/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/tests/ChoreographerTests/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/ChoreographerTests/src/main/res/mipmap-xhdpi/ic_launcher.png b/tests/ChoreographerTests/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/tests/ChoreographerTests/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/ChoreographerTests/src/main/res/mipmap-xxhdpi/ic_launcher.png b/tests/ChoreographerTests/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/tests/ChoreographerTests/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/ChoreographerTests/src/main/res/values/strings.xml b/tests/ChoreographerTests/src/main/res/values/strings.xml
new file mode 100644
index 0000000..e66b001
--- /dev/null
+++ b/tests/ChoreographerTests/src/main/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+    <string name="app_name">ChoreographerTests</string>
+</resources>